Skip to content

Commit

Permalink
Added facies fractions to VolumetricAnalysis
Browse files Browse the repository at this point in the history
  • Loading branch information
tnatt committed Nov 15, 2023
1 parent bdaa9e0 commit ef1a699
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 43 deletions.
5 changes: 5 additions & 0 deletions webviz_subsurface/_figures/px_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import plotly.graph_objects as go
from pandas.api.types import is_numeric_dtype

VALID_BOXMODES = ["group", "overlay"]


def create_figure(plot_type: str, **kwargs: Any) -> go.Figure:
"""Create subplots for selected parameters"""
Expand Down Expand Up @@ -33,6 +35,9 @@ def set_default_args(**plotargs: Any) -> dict:
plotargs["barmode"] = plotargs.get("barmode", "group")
plotargs["opacity"] = plotargs.get("opacity", 0.7)

if "boxmode" in plotargs and plotargs["boxmode"] not in VALID_BOXMODES:
plotargs["boxmode"] = VALID_BOXMODES[0]

if plotargs.get("facet_col") is not None:
facet_cols = plotargs["data_frame"][plotargs["facet_col"]].nunique()
plotargs.update(
Expand Down
73 changes: 57 additions & 16 deletions webviz_subsurface/_models/inplace_volumes_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ def __init__(
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
)
volumes_table = self._compute_water_zone_volumes(volumes_table)

# stack dataframe on fluid zone and add fluid as column instead of a column suffix
dfs = []
Expand Down Expand Up @@ -201,15 +199,20 @@ def parameters(self) -> List[str]:
return self.pmodel.parameters

@staticmethod
def _compute_water_zone_volumes(
voldf: pd.DataFrame, selectors: list
) -> pd.DataFrame:
def _compute_water_zone_volumes(voldf: pd.DataFrame) -> pd.DataFrame:
"""Compute water zone volumes by subtracting HC-zone volumes from
TOTAL volumes"""
supported_columns = ["BULK_TOTAL", "NET_TOTAL", "PORE_TOTAL", "PORV_TOTAL"]
supported_responses_in_df = [
col.replace("_TOTAL", "") for col in supported_columns if col in voldf
]
# Format check
for src, df in voldf.groupby("SOURCE"):
volcols = [col for col in df if col not in selectors]
volcols = [
col
for col in df
if any(col.startswith(resp) for resp in supported_responses_in_df)
]
if not any(col in volcols for col in supported_columns):
continue
if df[volcols].isnull().values.any():
Expand All @@ -221,7 +224,7 @@ def _compute_water_zone_volumes(
)
return voldf

for col in [x.replace("_TOTAL", "") for x in voldf if x in supported_columns]:
for col in supported_responses_in_df:
voldf[f"{col}_WATER"] = (
voldf[f"{col}_TOTAL"]
- voldf.get(f"{col}_OIL", 0)
Expand All @@ -247,6 +250,9 @@ def _set_initial_property_columns(self) -> None:
if all(col in self._dataframe for col in ["HCPV", "PORV"]):
self._property_columns.append("SW")

if "FACIES" in self.selectors and "BULK" in self._dataframe:
self._property_columns.append("FACIES_FRACTION")

for vol_column in ["STOIIP", "GIIP"]:
if all(col in self._dataframe for col in ["HCPV", vol_column]):
pvt = "BO" if vol_column == "STOIIP" else "BG"
Expand Down Expand Up @@ -276,11 +282,11 @@ def compute_property_columns(
dframe.replace(np.inf, np.nan, inplace=True)
return dframe

def get_df(
def _get_dataframe_with_volumetrics_and_properties(
self,
filters: Optional[Dict[str, list]] = None,
groups: Optional[list] = None,
parameters: Optional[list] = None,
filters: Dict[str, list],
groups: list,
parameters: list,
properties: Optional[list] = None,
) -> pd.DataFrame:
"""Function to retrieve a dataframe with volumetrics and properties. Parameters
Expand All @@ -290,10 +296,6 @@ def get_df(
"""
dframe = self.dataframe.copy()

groups = groups if groups is not None else []
filters = filters if filters is not None else {}
parameters = parameters if parameters is not None else []

if parameters and self.parameters:
columns = parameters + ["REAL", "ENSEMBLE"]
dframe = pd.merge(
Expand All @@ -319,8 +321,47 @@ def get_df(
dframe["BO"] = np.nan
if not filters.get("FLUID_ZONE") == ["gas"]:
dframe["BG"] = np.nan
if "FACIES" not in groups:
dframe["FACIES_FRACTION"] = np.nan

return dframe

def get_df(
self,
filters: Optional[Dict[str, list]] = None,
groups: Optional[list] = None,
parameters: Optional[list] = None,
properties: Optional[list] = None,
) -> pd.DataFrame:
"""
Function to retrieve a dataframe with volumetrics and properties, using the
"_get_dataframe_with_volumetrics_and_properties" method. If FACIES is used as
a group selector, the returning dataframe will include facies fractions.
Note if FACIES has been included as filter this filter is applied after
calculating facies fractions.
"""

groups = groups if groups is not None else []
filters = filters if filters is not None else {}
parameters = parameters if parameters is not None else []

if "FACIES" not in groups:
return self._get_dataframe_with_volumetrics_and_properties(
filters, groups, parameters, properties
)

if filters:
facies_filter = filters.pop("FACIES")

dframe = self._get_dataframe_with_volumetrics_and_properties(
filters, groups, parameters, properties
)
groups = [x for x in groups if x != "FACIES"]
df = dframe.groupby(groups) if groups else dframe
dframe["FACIES_FRACTION"] = df["BULK"].transform(lambda x: x / x.sum())

return dframe[dframe["FACIES"].isin(facies_filter)] if filters else dframe


def filter_df(dframe: pd.DataFrame, filters: dict) -> pd.DataFrame:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ def comparison_callback(
):
groupby.append("FLUID_ZONE")

if selections["Response"] == "FACIES_FRACTION" and "FACIES" not in groupby:
groupby.append("FACIES")

if display_option == "multi-response table":
# select max one hc_response for a cleaner table
responses = [selections["Response"]] + [
Expand Down Expand Up @@ -287,7 +290,6 @@ def create_comparison_df(
df[col, "diff (%)"] = ((df[col][value2] / df[col][value1]) - 1) * 100
df.loc[df[col]["diff"] == 0, (col, "diff (%)")] = 0
df = df[responses].replace([np.inf, -np.inf], np.nan).reset_index()

# remove rows where the selected response is nan
# can happen for properties where the volume columns are 0
df = df.loc[~((df[resp][value1].isna()) & (df[resp][value2].isna()))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
from webviz_subsurface._models import InplaceVolumesModel

from ..utils.table_and_figure_utils import (
FLUID_COLORS,
create_data_table,
create_table_columns,
fluid_annotation,
get_text_format_bar_plot,
)
from ..utils.utils import move_to_end_of_list, to_ranges
from ..views.distribution_main_layout import (
Expand Down Expand Up @@ -67,6 +69,12 @@ def _update_page_custom(selections: dict, page_selected: str) -> tuple:
"oil" if "BO" in selected_data else "gas"
]

if "FACIES_FRACTION" in selected_data and "FACIES" not in groups:
return html.Div(
"To plot FACIES_FRACTIONS, select 'FACIES' as response, subplot or color",
style={"margin-top": "40px"},
)

dframe = volumemodel.get_df(
filters=selections["filters"], groups=groups, parameters=parameters
)
Expand Down Expand Up @@ -98,8 +106,16 @@ def _update_page_custom(selections: dict, page_selected: str) -> tuple:
color=selections["Color by"],
color_discrete_sequence=selections["Colorscale"],
color_continuous_scale=selections["Colorscale"],
color_discrete_map=FLUID_COLORS
if selections["Color by"] == "FLUID_ZONE"
else None,
barmode=selections["barmode"],
boxmode=selections["barmode"],
text_auto=get_text_format_bar_plot(
selected_data, selections, volumemodel
)
if selections["Plot type"] == "bar"
else False,
layout=dict(
title=dict(
text=(
Expand Down Expand Up @@ -231,16 +247,16 @@ def _update_page_per_zr(selections: dict, page_selected: str) -> list:
layout={"bargap": 0.05},
color_discrete_sequence=selections["Colorscale"],
color=selections["Color by"],
text=selections["X Response"],
xaxis=dict(type="category", tickangle=45, tickfont_size=17, title=None),
).update_traces(
texttemplate=(
"%{text:.3s}"
if selections["X Response"] in volumemodel.volume_columns
else "%{text:.3g}"
text_auto=get_text_format_bar_plot(
responses=[selections["X Response"]],
selections=selections,
volumemodel=volumemodel,
),
textposition="auto",
)
color_discrete_map=FLUID_COLORS
if selections["Color by"] == "FLUID_ZONE"
else None,
).update_layout(margin_t=35)

if selections["X Response"] not in volumemodel.hc_responses:
barfig.add_annotation(fluid_annotation(selections))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ def _store_initial_load_info(
{"id": get_uuid("selections"), "tab": "voldist", "selector": "Color by"},
"value",
),
Input(
{"id": get_uuid("selections"), "tab": "voldist", "selector": "X Response"},
"value",
),
State(
{"id": get_uuid("selections"), "tab": "voldist", "selector": "bottom_viz"},
"options",
Expand All @@ -143,17 +147,20 @@ def _plot_options(
plot_type: str,
selected_page: str,
selected_color_by: list,
selected_x_response: str,
visualization_options: list,
selector_values: list,
selector_ids: list,
previous_selection: Optional[dict],
selected_tab: str,
) -> tuple:
ctx = callback_context.triggered[0]
if (
selected_tab != "voldist"
or ("Color by" in ctx["prop_id"] and plot_type not in ["box", "bar"])
or previous_selection is None

if selected_tab != "voldist" or previous_selection is None:
raise PreventUpdate

if ("Color by" in ctx["prop_id"] and plot_type not in ["box", "bar"]) or (
"X Response" in ctx["prop_id"] and selected_x_response != "FACIES_FRACTION"
):
raise PreventUpdate

Expand Down Expand Up @@ -184,6 +191,10 @@ def _plot_options(

settings[selector] = {"disable": disable, "value": value}

# Need to ensure a plot type is selected if page is custopm
if settings["Plot type"]["value"] is None and selected_page == "custom":
settings["Plot type"]["value"] = "histogram"

# update dropdown options based on plot type
if settings["Plot type"]["value"] == "scatter":
y_elm = x_elm = (
Expand Down Expand Up @@ -217,6 +228,11 @@ def _plot_options(
settings["Color by"]["options"] = [
{"label": elm, "value": elm} for elm in colorby_elm
]
if settings["X Response"]["value"] == "FACIES_FRACTION":
if selected_page == "per_zr":
settings["Color by"]["value"] = "FACIES"
elif selected_page == "conv":
settings["Subplots"]["value"] = "FACIES"

# disable vizualisation radioitem for some pages
for x in visualization_options:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ..views.tornado_view import tornado_error_layout, tornado_plots_layout


# pylint: disable=too-many-locals, too-many-statements
# pylint: disable=too-many-locals, too-many-statements, too-many-branches
def tornado_controllers(
get_uuid: Callable, volumemodel: InplaceVolumesModel, theme: WebvizConfigTheme
) -> None:
Expand All @@ -49,6 +49,23 @@ def _update_tornado_pages(

filters = selections["filters"].copy()

if selections["Response"] == "FACIES_FRACTION" and "FACIES" not in groups:
if len(filters.get("FACIES", [])) == 1:
groups.append("FACIES")
else:
return update_relevant_components(
id_list=id_list,
update_info=[
{
"new_value": tornado_error_layout(
"To see tornado for FACIES_FRACTION. Either select "
"'FACIES' as subplot value or filter to only one facies"
),
"conditions": {"page": page_selected},
}
],
)

figures = []
tables = []
responses = (
Expand Down Expand Up @@ -340,10 +357,4 @@ def create_tornado_table(

columns = create_table_columns(columns=[subplots]) if subplots is not None else []
columns.extend(tornado_table.columns)
columns.extend(
create_table_columns(
columns=["Reference"],
use_si_format=["Reference"] if use_si_format else [],
)
)
return table_data, columns
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
import webviz_core_components as wcc
from dash import dash_table

from webviz_subsurface._models import InplaceVolumesModel

FLUID_COLORS = {
"oil": "#007079",
"gas": "#FF1243",
"water": "#ADD8E6",
}


def create_table_columns(
columns: list,
Expand Down Expand Up @@ -85,11 +93,6 @@ def create_data_table(


def fluid_table_style() -> list:
fluid_colors = {
"oil": "#007079",
"gas": "#FF1243",
"water": "#ADD8E6",
}
return [
{
"if": {
Expand All @@ -99,7 +102,7 @@ def fluid_table_style() -> list:
"color": color,
"fontWeight": "bold",
}
for fluid, color in fluid_colors.items()
for fluid, color in FLUID_COLORS.items()
]


Expand Down Expand Up @@ -153,3 +156,19 @@ def update_tornado_figures_xaxis(figures: List[go.Figure]) -> None:
x_absmax = max([max(abs(trace.x)) for fig in figures for trace in fig.data])
for fig in figures:
fig.update_layout(xaxis_range=[-x_absmax, x_absmax])


def get_text_format_bar_plot(
responses: list, selections: dict, volumemodel: InplaceVolumesModel
) -> Union[bool, str]:
"""Get number format for bar plot labels"""
if not selections["textformat"]:
return False

if selections["textformat"] == "default":
if any(x in responses for x in volumemodel.volume_columns):
return f".{selections['decimals']}s"
if any(x in responses for x in volumemodel.property_columns):
return f".{selections['decimals']}f"

return f".{selections['decimals']}{selections['textformat']}"
Loading

0 comments on commit ef1a699

Please sign in to comment.