Skip to content

Commit

Permalink
Support eclipse volumes in VolumetricAnalysis (#770)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnatt authored Sep 21, 2021
1 parent dc50f8e commit 9621b0f
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 92 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED] - YYYY-MM-DD
### Added
- [#770](https://github.com/equinor/webviz-subsurface/pull/770) - Added support for dynamic volumetric files in `VolumetricAnalysis` and possibility of combining static and dynamic volumes on a comparable level. To trigger this behaviour a fipfile with `FIPNUM` to `REGION/ZONE` mapping information needs to be provided. Also added support for giving multiple files as input per source.
- [#755](https://github.com/equinor/webviz-subsurface/pull/755) - Updated existing and added new tests for the Drogon dataset.

### Changed
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"defusedxml>=0.6.0",
"ecl2df>=0.13.0; sys_platform=='linux'",
"fmu-ensemble>=1.2.3",
"fmu-tools>=1.8",
"opm>=2020.10.1; sys_platform=='linux'",
"pandas>=1.1.5",
"pillow>=6.1",
Expand Down
105 changes: 66 additions & 39 deletions webviz_subsurface/_models/inplace_volumes_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class InplaceVolumesModel:
"SOURCE",
"ENSEMBLE",
"REAL",
"FIPNUM",
"SET",
"ZONE",
"REGION",
"FACIES",
Expand All @@ -40,48 +42,48 @@ def __init__(
parameter_table: Optional[pd.DataFrame] = None,
non_net_facies: Optional[List[str]] = None,
drop_constants: bool = True,
volume_type: str = "static",
):
self._volume_type = volume_type
self.pmodel = ParametersModel(
parameter_table, drop_constants=drop_constants, keep_numeric_only=False
)
selectors = [x for x in volumes_table.columns if x in self.POSSIBLE_SELECTORS]

# It is not yet supported to combine sources with different selectors
if volumes_table[selectors].isnull().values.any():
raise TypeError(
f"Selectors {[x for x in selectors if x not in ['ENSEMBLE', 'SOURCE', 'REAL']]} "
"needs to be defined for all sources"
)

# compute water zone volumes if total volumes are present
if any(col.endswith("_TOTAL") for col in volumes_table.columns):
volumes_table = self._compute_water_zone_volumes(volumes_table, selectors)
if volume_type != "dynamic":
# compute water zone volumes if total volumes are present
if any(col.endswith("_TOTAL") for col in volumes_table.columns):
volumes_table = self._compute_water_zone_volumes(
volumes_table, selectors
)

# stack dataframe on fluid zone and add fluid as column istead of a column suffix
dfs = []
for fluid in ["OIL", "GAS", "WATER"]:
fluid_columns = [
x for x in volumes_table.columns if x.endswith(f"_{fluid}")
]
if not fluid_columns:
continue
df = volumes_table[selectors + fluid_columns].copy()
df.columns = df.columns.str.replace(f"_{fluid}", "")
df["FLUID_ZONE"] = fluid.lower()
# Rename PORE to PORV (PORE will be deprecated..)
if "PORE" in df:
df.rename(columns={"PORE": "PORV"}, inplace=True)
dfs.append(df)
self._dataframe = pd.concat(dfs)

# Set NET volumes based on facies if non_net_facies in input
if non_net_facies is not None and "FACIES" in self._dataframe:
self._dataframe["NET"] = self._dataframe["BULK"]
self._dataframe.loc[
self._dataframe["FACIES"].isin(non_net_facies), "NET"
] = 0

# If snesitivity run merge sensitivity columns into the dataframe
# stack dataframe on fluid zone and add fluid as column istead of a column suffix
dfs = []
for fluid in ["OIL", "GAS", "WATER"]:
fluid_columns = [
x for x in volumes_table.columns if x.endswith(f"_{fluid}")
]
if not fluid_columns:
continue
df = volumes_table[selectors + fluid_columns].copy()
df.columns = df.columns.str.replace(f"_{fluid}", "")
df["FLUID_ZONE"] = fluid.lower()
dfs.append(df)
self._dataframe = pd.concat(dfs)

# Set NET volumes based on facies if non_net_facies in input
if non_net_facies is not None and "FACIES" in self._dataframe:
self._dataframe["NET"] = self._dataframe["BULK"]
self._dataframe.loc[
self._dataframe["FACIES"].isin(non_net_facies), "NET"
] = 0
else:
self._dataframe = volumes_table
# Workaround the FUID ZONE needs to be defined in the
# VolumetricAnalysis plugin - this will be fixed later!
self._dataframe["FLUID_ZONE"] = "-"

# If sensitivity run merge sensitivity columns into the dataframe
if self.pmodel.sensrun:
self._dataframe = pd.merge(
self._dataframe, self.pmodel.sens_df, on=["ENSEMBLE", "REAL"]
Expand Down Expand Up @@ -112,6 +114,10 @@ def parameter_df(self) -> pd.DataFrame:
def sensrun(self) -> bool:
return self.pmodel.sensrun

@property
def volume_type(self) -> str:
return self._volume_type

@property
def sensitivities(self) -> List[str]:
return self.pmodel.sensitivities
Expand Down Expand Up @@ -281,15 +287,25 @@ def filter_df(dframe: pd.DataFrame, filters: dict) -> pd.DataFrame:


def extract_volumes(
ensemble_set_model: EnsembleSetModel, volfolder: str, volfiles: Dict[str, Any]
ensemble_set_model: EnsembleSetModel,
volfolder: str,
volfiles: Dict[str, Any],
) -> pd.DataFrame:
"""Aggregates volumetric files from an FMU ensemble.
Files must be stored on standardized csv format.
"""

dfs = []
for volname, volfile in volfiles.items():
df = ensemble_set_model.load_csv(Path(volfolder) / volfile)
for volname, files in volfiles.items():
if isinstance(files, list):
volframes = [
ensemble_set_model.load_csv(Path(volfolder) / volfile)
for volfile in files
]
df = merge_csv_files(volframes)
elif isinstance(files, str):
df = ensemble_set_model.load_csv(Path(volfolder) / files)
else:
raise ValueError("Wrong format of volfile value argument!")
df["SOURCE"] = volname
dfs.append(df)

Expand All @@ -299,3 +315,14 @@ def extract_volumes(
f"Ensure that the files are present in relative folder {volfolder}"
)
return pd.concat(dfs)


def merge_csv_files(volframes: List[pd.DataFrame]) -> pd.DataFrame:
"""Merge csv files on common columns"""
common_columns = list(
set.intersection(*[set(frame.columns) for frame in volframes])
)
merged_dframe = pd.DataFrame(columns=common_columns)
for frame in volframes:
merged_dframe = pd.merge(merged_dframe, frame, on=common_columns, how="outer")
return merged_dframe
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,19 @@ def one_plot_one_table_layout(uuid: str) -> html.Div:
def plots_per_zone_region_layout(
uuid: str, volumemodel: InplaceVolumesModel
) -> html.Div:
selectors = [x for x in ["ZONE", "REGION", "FACIES"] if x in volumemodel.selectors]
height = max(88 / len(selectors), 25)
selectors = [
x
for x in ["ZONE", "REGION", "FACIES", "FIPNUM", "SET"]
if x in volumemodel.selectors
]
height = "42vh" if len(selectors) < 3 else "25vh"
layout = []
for selector in selectors:
layout.append(
wcc.Frame(
color="white",
highlight=False,
style={"height": f"{height}vh"},
style={"height": height},
children=wcc.FlexBox(
children=[
html.Div(
Expand All @@ -136,7 +140,7 @@ def plots_per_zone_region_layout(
"page": "per_zr",
},
config={"displayModeBar": False},
style={"height": f"{height}vh"},
style={"height": height},
),
),
html.Div(
Expand All @@ -149,7 +153,7 @@ def plots_per_zone_region_layout(
"page": "per_zr",
},
config={"displayModeBar": False},
style={"height": f"{height}vh"},
style={"height": height},
),
),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ def selections_layout(
theme: WebvizConfigTheme,
tab: str,
) -> html.Div:
"""Layout for selecting intersection data"""
selectors = "/".join(
[x.lower() for x in ["ZONE", "REGION", "FACIES"] if x in volumemodel.selectors]
[
x.lower()
for x in ["ZONE", "REGION", "FACIES", "FIPNUM", "SET"]
if x in volumemodel.selectors
]
)
return html.Div(
children=[
Expand All @@ -29,7 +32,7 @@ def selections_layout(
],
),
plot_selections_layout(uuid, volumemodel, tab),
settings_layout(uuid, theme, tab),
settings_layout(volumemodel, uuid, theme, tab),
]
)

Expand Down Expand Up @@ -145,14 +148,16 @@ def plot_selector_dropdowns(
return dropdowns


def settings_layout(uuid: str, theme: WebvizConfigTheme, tab: str) -> wcc.Selectors:
def settings_layout(
volumemodel: InplaceVolumesModel, uuid: str, theme: WebvizConfigTheme, tab: str
) -> wcc.Selectors:

theme_colors = theme.plotly_theme.get("layout", {}).get("colorway", [])
return wcc.Selectors(
label="⚙️ SETTINGS",
open_details=False,
children=[
remove_fluid_annotation(uuid=uuid, tab=tab),
remove_fluid_annotation(volumemodel, uuid=uuid, tab=tab),
subplot_xaxis_range(uuid=uuid, tab=tab),
histogram_options(uuid=uuid, tab=tab),
html.Span("Colors", style={"font-weight": "bold"}),
Expand Down Expand Up @@ -197,13 +202,18 @@ def table_sync_option(uuid: str, tab: str) -> html.Div:
)


def remove_fluid_annotation(uuid: str, tab: str) -> html.Div:
def remove_fluid_annotation(
volumemodel: InplaceVolumesModel, uuid: str, tab: str
) -> html.Div:
return html.Div(
style={"margin-bottom": "10px"},
style={
"margin-bottom": "10px",
"display": "none" if volumemodel.volume_type == "dynamic" else "block",
},
children=wcc.Checklist(
id={"id": uuid, "tab": tab, "selector": "Fluid annotation"},
options=[{"label": "Show fluid annotation", "value": "Show"}],
value=["Show"],
value=["Show"] if volumemodel.volume_type != "dynamic" else [],
),
)

Expand Down
Loading

0 comments on commit 9621b0f

Please sign in to comment.