Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Well analysis plugin #981

Merged
merged 40 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d756372
Basic file structure of new plugin
Feb 12, 2022
c050439
implemented the summary data provider
Feb 13, 2022
cf74a02
main layout for well control tab
Feb 13, 2022
d3aeebf
well control layout in separate file
Feb 13, 2022
5bb2d51
factored out well overview layout in separate file
Feb 13, 2022
8baa417
removed frame from controls
Feb 13, 2022
0cb2ab2
split callbacks on two files
Feb 13, 2022
a5e0ac8
added well_control_figure
Feb 13, 2022
557bea9
added node info code
Feb 13, 2022
8b633cd
gruptree model class implemented
Feb 14, 2022
dd67f89
started implementing the well control callbacks
Feb 15, 2022
d4b84ad
Well control plot implemented
Feb 16, 2022
d527294
basic setup for the well overview charts
Feb 17, 2022
c5928f1
first version of the well overview barchart
Feb 18, 2022
a248cb9
pylint and mypy issues
Feb 18, 2022
0d03ee4
further improvements
Feb 19, 2022
9011f0a
Some small improvements
Feb 21, 2022
865ebc2
chart type buttons logic implemented
Feb 24, 2022
54f86e7
started implementing pie charts and many other improvements
Feb 25, 2022
fcd530b
pie chart implemented
Feb 25, 2022
c7b3fc7
merged prod plots into one class
Feb 26, 2022
8b0ab40
more chart formatting
Feb 26, 2022
e2f129d
implemented display of only charttype settings
Feb 27, 2022
831d1a6
implemented area chart
Feb 28, 2022
0fc0369
Well filter implemented
Mar 2, 2022
6a2cd1e
Improved figure formatting
Mar 3, 2022
c451758
renamed chart to figure
Mar 3, 2022
23a1ac7
network pressures not added multiple times if they are in multiple ne…
Mar 4, 2022
06fbee2
plugin docstring and renamed _ensemble_data.py to _ensemble_well_anal…
Mar 6, 2022
c84390d
implemented webvizstore
Mar 7, 2022
98c1405
implemented webvizstore
Mar 7, 2022
cdc5b3b
standardized oil, water, gas colors
Mar 7, 2022
8dd9b84
docstrings and some improvements
Mar 8, 2022
ff3bb0a
plot formatting without reloading the data
Mar 10, 2022
dc1557b
various improvements
Mar 10, 2022
2431c4e
improved class docstring
Mar 21, 2022
261503d
Changelog entry
Mar 21, 2022
19f205e
temporarily shift testdata branch
Mar 22, 2022
1034521
fixed bug in testdata branch name
Mar 22, 2022
68a4a42
changed repo/branch back to equinor/master
Mar 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED] - YYYY-MM-DD

### Added

- [#981](https://github.com/equinor/webviz-subsurface/pull/981) - `WellAnalysis` - New plugin well analysis. One tab for well production overview plots and one for well control mode and network analysis.

### Fixed
- [#985](https://github.com/equinor/webviz-subsurface/pull/985) - `WellLogViewer` - Updated data format to latest version. Requires no changes in input data.

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"AssistedHistoryMatchingAnalysis = webviz_subsurface.plugins:AssistedHistoryMatchingAnalysis",
"WellCompletions = webviz_subsurface.plugins:WellCompletions",
"WellLogViewer = webviz_subsurface.plugins:WellLogViewer",
"WellAnalysis = webviz_subsurface.plugins:WellAnalysis",
],
"console_scripts": ["smry2arrow_batch=webviz_subsurface.smry2arrow_batch:main"],
},
Expand Down
1 change: 1 addition & 0 deletions webviz_subsurface/_models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .ensemble_model import EnsembleModel
from .ensemble_set_model import EnsembleSetModel
from .gruptree_model import GruptreeModel
from .inplace_volumes_model import InplaceVolumesModel
from .observation_model import ObservationModel
from .parameter_model import ParametersModel
Expand Down
102 changes: 102 additions & 0 deletions webviz_subsurface/_models/gruptree_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from pathlib import Path
from typing import Callable, Dict, List, Tuple

import pandas as pd
from webviz_config.webviz_store import webvizstore

from webviz_subsurface._datainput.fmu_input import scratch_ensemble


class GruptreeModel:
"""Facilitates loading of gruptree tables. Can be reused in all
plugins that are using grouptree data and extended with additional
functionality and filtering options if necessary.
"""

def __init__(
self,
ens_name: str,
ens_path: Path,
gruptree_file: str,
remove_gruptree_if_branprop: bool = True,
):
self._ens_name = ens_name
self._ens_path = ens_path
self._gruptree_file = gruptree_file
self._remove_gruptree_if_branprop = remove_gruptree_if_branprop
self._dataframe = self.read_ensemble_gruptree()

@property
def dataframe(self) -> pd.DataFrame:
return self._dataframe

def __repr__(self) -> str:
"""This is necessary for webvizstore to work on objects"""
return f"""
GruptreeDataModel {self._ens_name} {self._ens_path} {self._gruptree_file}
"""

@property
def webviz_store(self) -> Tuple[Callable, List[Dict]]:
return (
self.read_ensemble_gruptree,
[
{
"self": self,
}
],
)

@webvizstore
def read_ensemble_gruptree(self) -> pd.DataFrame:
"""Reads the gruptree files for an ensemble from the scratch disk. These
files can be exported in the FMU workflow using the ECL2CSV
forward model with subcommand gruptree.

If BRANPROP is found in the KEYWORD column, then GRUPTREE rows
are filtered out.

If the trees are equal in every realization, only one realization is kept.
"""

ens = scratch_ensemble(self._ens_name, self._ens_path, filter_file="OK")
df_files = ens.find_files(self._gruptree_file)

if df_files.empty:
return pd.DataFrame()
# raise ValueError(f"No gruptree file available for ensemble: {ens_name}")

# Load all gruptree dataframes and check if they are equal
compare_columns = ["DATE", "CHILD", "KEYWORD", "PARENT"]
df_prev = pd.DataFrame()
dataframes = []
gruptrees_are_equal = True
for i, row in df_files.iterrows():
df_real = pd.read_csv(row["FULLPATH"])

if (
self._remove_gruptree_if_branprop
and "BRANPROP" in df_real["KEYWORD"].unique()
):
df_real = df_real[df_real["KEYWORD"] != "GRUPTREE"]

if (
i > 0
and gruptrees_are_equal
and not df_real[compare_columns].equals(df_prev)
):
gruptrees_are_equal = False
else:
df_prev = df_real[compare_columns].copy()

df_real["REAL"] = row["REAL"]
dataframes.append(df_real)
df = pd.concat(dataframes)

# Return either one or all realization in a common dataframe
if gruptrees_are_equal:
df = df[df["REAL"] == df["REAL"].min()]

df["DATE"] = pd.to_datetime(df["DATE"])

return df.where(pd.notnull(df), None)
7 changes: 7 additions & 0 deletions webviz_subsurface/_utils/colors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import colorsys
from enum import Enum
from typing import Tuple


class StandardColors(Enum):
OIL_GREEN = "#2ca02c"
WATER_BLUE = "#1f77b4"
GAS_RED = "#d62728"
anders-kiaer marked this conversation as resolved.
Show resolved Hide resolved


def hex_to_rgb(hex_string: str) -> Tuple[float, float, float]:
"""Converts the given hex color to rgb tuple with floating point byte color values.

Expand Down
1 change: 1 addition & 0 deletions webviz_subsurface/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from ._surface_with_seismic_cross_section import SurfaceWithSeismicCrossSection
from ._tornado_plotter_fmu import TornadoPlotterFMU
from ._volumetric_analysis import VolumetricAnalysis
from ._well_analysis import WellAnalysis
from ._well_completions import WellCompletions
from ._well_cross_section import WellCrossSection
from ._well_cross_section_fmu import WellCrossSectionFMU
Expand Down
1 change: 1 addition & 0 deletions webviz_subsurface/plugins/_well_analysis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._plugin import WellAnalysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .well_control_callbacks import well_control_callbacks
from .well_overview_callbacks import well_overview_callbacks
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from typing import Any, Callable, Dict, List, Optional, Tuple

import webviz_core_components as wcc
from dash import Dash, Input, Output, State
from webviz_config import WebvizConfigTheme

from .._ensemble_well_analysis_data import EnsembleWellAnalysisData
from .._figures import create_well_control_figure
from .._layout import WellControlLayoutElements


def well_control_callbacks(
app: Dash,
get_uuid: Callable,
data_models: Dict[str, EnsembleWellAnalysisData],
theme: WebvizConfigTheme,
) -> None:
@app.callback(
Output(get_uuid(WellControlLayoutElements.WELL), "options"),
Output(get_uuid(WellControlLayoutElements.WELL), "value"),
Output(get_uuid(WellControlLayoutElements.REAL), "options"),
Output(get_uuid(WellControlLayoutElements.REAL), "value"),
Input(get_uuid(WellControlLayoutElements.ENSEMBLE), "value"),
)
def _update_dropdowns(
ensemble: str,
) -> Tuple[
List[Dict[str, str]], Optional[str], List[Dict[str, Any]], Optional[int]
]:
"""Updates the well and realization dropdowns with ensemble values"""
wells = data_models[ensemble].wells
reals = data_models[ensemble].realizations
return (
[{"label": well, "value": well} for well in wells],
wells[0],
[{"label": real, "value": real} for real in reals],
reals[0],
)

@app.callback(
Output(get_uuid(WellControlLayoutElements.GRAPH), "children"),
Input(get_uuid(WellControlLayoutElements.WELL), "value"),
Input(get_uuid(WellControlLayoutElements.INCLUDE_BHP), "value"),
Input(get_uuid(WellControlLayoutElements.MEAN_OR_REAL), "value"),
Input(get_uuid(WellControlLayoutElements.REAL), "value"),
Input(get_uuid(WellControlLayoutElements.CTRLMODE_BAR), "value"),
Input(get_uuid(WellControlLayoutElements.SHARED_XAXES), "value"),
State(get_uuid(WellControlLayoutElements.ENSEMBLE), "value"),
prevent_initial_call=True,
)
def _update_figure(
well: str,
include_bhp: List[str],
mean_or_single_real: str,
real: int,
display_ctrlmode_bar: bool,
shared_xaxes: List[str],
ensemble: str,
) -> List[Optional[Any]]:
"""Updates the well control figure"""
fig = create_well_control_figure(
data_models[ensemble].get_node_info(well),
data_models[ensemble].summary_data,
mean_or_single_real,
real,
display_ctrlmode_bar,
"shared_xaxes" in shared_xaxes,
"include_bhp" in include_bhp,
theme,
)

return wcc.Graph(style={"height": "87vh"}, figure=fig)

@app.callback(
Output(
get_uuid(WellControlLayoutElements.SINGLE_REAL_OPTIONS),
component_property="style",
),
Input(get_uuid(WellControlLayoutElements.MEAN_OR_REAL), "value"),
)
def _show_hide_single_real_options(mean_or_single_real: str) -> Dict[str, str]:
"""Hides or unhides the realization dropdown according to whether mean
or single realization is selected.
"""
if mean_or_single_real == "plot_mean":
return {"display": "none"}
return {"display": "block"}
Loading