From 939fa89a56cdb3013241c93bf3e2a10278e89d26 Mon Sep 17 00:00:00 2001 From: EirikPeirik <104941095+EirikPeirik@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:53:08 +0200 Subject: [PATCH] `ProdMisfit` moved to WLF (#1085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eirik Sundby Håland (OG SUB RPE) Co-authored-by: Roger Nybø --- CHANGELOG.md | 2 + .../plugins/_prod_misfit/_callbacks.py | 292 ------ .../plugins/_prod_misfit/_layout.py | 983 ------------------ .../plugins/_prod_misfit/_plugin.py | 246 ++++- .../plugins/_prod_misfit/_plugin_ids.py | 25 + .../_prod_misfit/shared_settings/__init__.py | 1 + .../_prod_misfit/shared_settings/_filter.py | 199 ++++ .../_prod_misfit/view_elements/__init__.py | 1 + .../_prod_misfit/view_elements/_graph.py | 22 + .../plugins/_prod_misfit/views/__init__.py | 7 + .../views/_production_misfit_per_real.py | 309 ++++++ .../_prod_misfit/views/_view_functions.py | 27 + .../views/_well_production_coverage.py | 285 +++++ .../views/_well_production_heatmap.py | 263 +++++ 14 files changed, 1359 insertions(+), 1303 deletions(-) delete mode 100644 webviz_subsurface/plugins/_prod_misfit/_callbacks.py delete mode 100644 webviz_subsurface/plugins/_prod_misfit/_layout.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/_plugin_ids.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/shared_settings/__init__.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/shared_settings/_filter.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/view_elements/__init__.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/view_elements/_graph.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/views/__init__.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/views/_production_misfit_per_real.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/views/_view_functions.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/views/_well_production_coverage.py create mode 100644 webviz_subsurface/plugins/_prod_misfit/views/_well_production_heatmap.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4de4173f2..24f74bf10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1086](https://github.com/equinor/webviz-subsurface/pull/1086) - Converted the `SimulationTimeSeries` plugin to WLF (Webviz Layout Framework). Also increase maximum number of initial vectors from 3 to 5. - [#1078](https://github.com/equinor/webviz-subsurface/pull/1078) - Converted the `PvtPlot` plugin to WLF (Webviz Layout Framework). - [#1092](https://github.com/equinor/webviz-subsurface/pull/1092) - Converted the `TornadoPlotterFMU` plugin to WLF (Webviz Layout Framework). +- [#1085](https://github.com/equinor/webviz-subsurface/pull/1085) - Converted the `ProdMisfit` plugin to WLF (Webviz Layout Framework). + ## [0.2.14] - 2022-06-28 diff --git a/webviz_subsurface/plugins/_prod_misfit/_callbacks.py b/webviz_subsurface/plugins/_prod_misfit/_callbacks.py deleted file mode 100644 index 96ffd9221..000000000 --- a/webviz_subsurface/plugins/_prod_misfit/_callbacks.py +++ /dev/null @@ -1,292 +0,0 @@ -from typing import Callable, Dict, List, Union - -import dash -import webviz_core_components as wcc -from dash.dependencies import Input, Output - -from .._simulation_time_series._views._subplot_view._utils.provider_set import ( - ProviderSet, -) -from ._layout import LayoutElements -from .utils import make_dataframes as makedf -from .utils import make_figures as makefigs - - -# pylint: disable = too-many-arguments, too-many-locals -def plugin_callbacks( - app: dash.Dash, - get_uuid: Callable, - input_provider_set: ProviderSet, - ens_vectors: Dict[str, List[str]], - ens_realizations: Dict[str, List[int]], - well_collections: Dict[str, List[str]], - weight_reduction_factor_oil: float, - weight_reduction_factor_wat: float, - weight_reduction_factor_gas: float, -) -> None: - - # -------------------------------------------- - # --- prod misfit --- - # -------------------------------------------- - @app.callback( - Output(get_uuid(LayoutElements.PROD_MISFIT_GRAPH), "children"), - Input(get_uuid(LayoutElements.PROD_MISFIT_ENSEMBLE_NAMES), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_DATES), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_PHASES), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_WELL_NAMES), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_WELL_COLLECTIONS), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_WELL_COMBINE_TYPE), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_REALIZATIONS), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_COLORBY), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_SORTING), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_FIGHEIGHT), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_OBS_ERROR_WEIGHT), "value"), - Input(get_uuid(LayoutElements.PROD_MISFIT_EXPONENT), "value"), - # Input(get_uuid(LayoutElements.PROD_MISFIT_NORMALIZATION), "value"), - # prevent_initial_call=True, - ) - def _update_prod_misfit_graph( - ensemble_names: List[str], - selector_dates: list, - selector_phases: list, - selector_well_names: list, - selector_well_collection_names: list, - selector_well_combine_type: str, - selector_realizations: List[int], - colorby: str, - sorting: str, - figheight: int, - obs_error_weight: float, - misfit_exponent: float, - # misfit_normalization: bool, - ) -> Union[str, List[wcc.Graph]]: - - if not ensemble_names: - return "No ensembles selected" - - well_names = _get_well_names_combined( - well_collections, - selector_well_collection_names, - selector_well_names, - selector_well_combine_type, - ) - - dframe = makedf.get_df_diff( - makedf.get_df_smry( - input_provider_set, - ensemble_names, - ens_vectors, - ens_realizations, - selector_realizations, - well_names, - selector_phases, - selector_dates, - ), - obs_error_weight, - weight_reduction_factor_oil, - weight_reduction_factor_wat, - weight_reduction_factor_gas, - misfit_exponent, - ) - - figures = makefigs.prod_misfit_plot( - dframe, - selector_phases, - colorby, - sorting, - figheight, - misfit_exponent, - # misfit_normalization, - ) - - return figures - - # -------------------------------------------- - # --- well coverage --- - # -------------------------------------------- - @app.callback( - Output(get_uuid(LayoutElements.WELL_COVERAGE_GRAPH), "children"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_ENSEMBLE_NAMES), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_DATES), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_PHASES), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_WELL_NAMES), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_WELL_COLLECTIONS), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_WELL_COMBINE_TYPE), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_REALIZATIONS), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_COLORBY), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_PLOT_TYPE), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_FIGHEIGHT), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_BOXMODE), "value"), - Input(get_uuid(LayoutElements.WELL_COVERAGE_BOXPLOT_POINTS), "value"), - # prevent_initial_call=True, - ) - def _update_well_coverage_graph( - ensemble_names: List[str], - selector_dates: List[str], - selector_phases: List[str], - selector_well_names: List[str], - selector_well_collection_names: List[str], - selector_well_combine_type: str, - selector_realizations: List[int], - colorby: str, - plot_type: str, - figheight: int, - boxmode: str, - boxplot_points: str, - ) -> Union[str, List[wcc.Graph]]: - - if not ensemble_names: - return "No ensembles selected" - - well_names = _get_well_names_combined( - well_collections, - selector_well_collection_names, - selector_well_names, - selector_well_combine_type, - ) - - if plot_type in ["diffplot", "rel_diffplot"]: - relative_diff = plot_type == "rel_diffplot" - dframe = makedf.get_df_diff( - makedf.get_df_smry( - input_provider_set, - ensemble_names, - ens_vectors, - ens_realizations, - selector_realizations, - well_names, - selector_phases, - selector_dates, - ), - relative_diff=relative_diff, - ) - figures = makefigs.coverage_diffplot( - dframe, - selector_phases, - colorby, - vector_type="well", - figheight=figheight, - boxmode=boxmode, - boxplot_points=boxplot_points, - ) - if plot_type == "crossplot": - dframe = makedf.get_df_smry( - input_provider_set, - ensemble_names, - ens_vectors, - ens_realizations, - selector_realizations, - well_names, - selector_phases, - selector_dates, - ) - figures = makefigs.coverage_crossplot( - dframe, - selector_phases, - colorby, - vector_type="well", - figheight=figheight, - boxplot_points=boxplot_points, - ) - - return figures - - # -------------------------------------------- - # --- heatmap --- - # -------------------------------------------- - @app.callback( - Output(get_uuid(LayoutElements.HEATMAP_GRAPH), "children"), - Input(get_uuid(LayoutElements.HEATMAP_ENSEMBLE_NAMES), "value"), - Input(get_uuid(LayoutElements.HEATMAP_DATES), "value"), - Input(get_uuid(LayoutElements.HEATMAP_PHASES), "value"), - Input(get_uuid(LayoutElements.HEATMAP_WELL_NAMES), "value"), - Input(get_uuid(LayoutElements.HEATMAP_WELL_COLLECTIONS), "value"), - Input(get_uuid(LayoutElements.HEATMAP_WELL_COMBINE_TYPE), "value"), - Input(get_uuid(LayoutElements.HEATMAP_REALIZATIONS), "value"), - Input(get_uuid(LayoutElements.HEATMAP_FILTER_LARGEST), "value"), - Input(get_uuid(LayoutElements.HEATMAP_PLOT_TYPE), "value"), - Input(get_uuid(LayoutElements.HEATMAP_FIGHEIGHT), "value"), - Input(get_uuid(LayoutElements.HEATMAP_SCALE_COL_RANGE), "value"), - # prevent_initial_call=True, - ) - def _update_heatmap_graph( - ensemble_names: List[str], - selector_dates: list, - selector_phases: list, - selector_well_names: list, - selector_well_collection_names: list, - selector_well_combine_type: str, - selector_realizations: list, - selector_filter_largest: int, - selector_plot_type: str, - selector_figheight: int, - selector_scale_col_range: float, - ) -> Union[str, List[wcc.Graph]]: - - if not ensemble_names: - return "No ensembles selected" - - well_names = _get_well_names_combined( - well_collections, - selector_well_collection_names, - selector_well_names, - selector_well_combine_type, - ) - - relative_diff = selector_plot_type == "rel_diffplot" - dframe = makedf.get_df_diff_stat( - makedf.get_df_diff( - makedf.get_df_smry( - input_provider_set, - ensemble_names, - ens_vectors, - ens_realizations, - selector_realizations, - well_names, - selector_phases, - selector_dates, - ), - relative_diff=relative_diff, - ) - ) - - figures = makefigs.heatmap_plot( - dframe, - selector_phases, - vector_type="well", - filter_largest=selector_filter_largest, - figheight=selector_figheight, - scale_col_range=selector_scale_col_range, - ) - return figures - - -# ---------------------- -# --- help functions --- -# ---------------------- - - -def _get_well_names_combined( - well_collections: Dict[str, List[str]], - selected_collection_names: list, - selected_wells: list, - combine_type: str = "intersection", -) -> List[str]: - """Return union or intersection of well list and well collection lists.""" - - selected_collection_wells = [] - for collection_name in selected_collection_names: - selected_collection_wells.extend(well_collections[collection_name]) - selected_collection_wells = list(set(selected_collection_wells)) - if combine_type == "intersection": - # find intersection of selector wells and selector well collections - well_names_combined = [ - well for well in selected_wells if well in selected_collection_wells - ] - else: - # find union of selector wells and selector well collections - well_names_combined = list(selected_collection_wells) - well_names_combined.extend(selected_wells) - well_names_combined = list(set(well_names_combined)) - - return well_names_combined diff --git a/webviz_subsurface/plugins/_prod_misfit/_layout.py b/webviz_subsurface/plugins/_prod_misfit/_layout.py deleted file mode 100644 index 82932c838..000000000 --- a/webviz_subsurface/plugins/_prod_misfit/_layout.py +++ /dev/null @@ -1,983 +0,0 @@ -from datetime import datetime -from typing import Callable, Dict, List - -import webviz_core_components as wcc -from dash import html - - -# pylint: disable=too-few-public-methods -class LayoutElements: - """Definition of names of HTML-elements in layout.""" - - PROD_MISFIT_LAYOUT = "prod_misfit_layout" - PROD_MISFIT_ENSEMBLE_NAMES = "prod_misfit_ensemble_names" - PROD_MISFIT_DATES = "prod_misfit_dates" - PROD_MISFIT_PHASES = "prod_misfit_phases" - PROD_MISFIT_WELL_NAMES = "prod_misfit_well_names" - PROD_MISFIT_WELL_COLLECTIONS = "prod_misfit_well_collections" - PROD_MISFIT_WELL_COMBINE_TYPE = "prod_misfit_well_combine_type" - PROD_MISFIT_REALIZATIONS = "prod_misfit_realizations" - PROD_MISFIT_COLORBY = "prod_misfit_colorby" - PROD_MISFIT_SORTING = "prod_misfit_sorting" - PROD_MISFIT_FIGHEIGHT = "prod_misfit_figheight" - PROD_MISFIT_OBS_ERROR_WEIGHT = "prod_misfit_obs_error_weight" - PROD_MISFIT_EXPONENT = "prod_misfit_exponent" - # PROD_MISFIT_NORMALIZATION = "prod_misfit_normalization" - PROD_MISFIT_GRAPH = "prod_misfit_graph" - - WELL_COVERAGE_LAYOUT = "well_coverage_layout" - WELL_COVERAGE_ENSEMBLE_NAMES = "well_coverage_ensemble_names" - WELL_COVERAGE_DATES = "well_coverage_dates" - WELL_COVERAGE_PHASES = "well_coverage_phases" - WELL_COVERAGE_WELL_NAMES = "well_coverage_well_names" - WELL_COVERAGE_WELL_COLLECTIONS = "well_coverage_well_collections" - WELL_COVERAGE_WELL_COMBINE_TYPE = "well_coverage_well_combine_type" - WELL_COVERAGE_REALIZATIONS = "well_coverage_realizations" - WELL_COVERAGE_COLORBY = "well_coverage_colorby" - WELL_COVERAGE_SORTING = "well_coverage_sorting" - WELL_COVERAGE_FIGHEIGHT = "well_coverage_figheight" - WELL_COVERAGE_PLOT_TYPE = "well_coverage_plot_type" - WELL_COVERAGE_BOXMODE = "well_coverage_boxmode" - WELL_COVERAGE_BOXPLOT_POINTS = "well_coverage_points" - WELL_COVERAGE_GRAPH = "well_coverage_graph" - - HEATMAP_LAYOUT = "heatmap_layout" - HEATMAP_ENSEMBLE_NAMES = "heatmap_ensemble_names" - HEATMAP_DATES = "heatmap_dates" - HEATMAP_PHASES = "heatmap_phases" - HEATMAP_WELL_NAMES = "heatmap_well_names" - HEATMAP_WELL_COLLECTIONS = "heatmap_well_collections" - HEATMAP_WELL_COMBINE_TYPE = "heatmap_well_combine_type" - HEATMAP_REALIZATIONS = "heatmap_realizations" - HEATMAP_FILTER_LARGEST = "heatmap_filter_largest" - HEATMAP_FIGHEIGHT = "heatmap_figheight" - HEATMAP_PLOT_TYPE = "heatmap_plot_type" - HEATMAP_SCALE_COL_RANGE = "heatmap_scale_col_range" - HEATMAP_GRAPH = "heatmap_graph" - - TABS_STYLES = {"height": "60px", "width": "100%"} - - TAB_STYLE = { - "borderBottom": "1px solid #d6d6d6", - "padding": "6px", - "fontWeight": "bold", - } - - TAB_SELECTED_STYLE = { - "borderTop": "1px solid #d6d6d6", - "borderBottom": "1px solid #d6d6d6", - "backgroundColor": "#007079", - "color": "white", - "padding": "6px", - } - - -# --- layout --- -def main_layout( - get_uuid: Callable, - ensemble_names: List[str], - dates: Dict[str, List[datetime]], - phases: Dict[str, List[str]], - wells: Dict[str, List[str]], - realizations: Dict[str, List[int]], - well_collections: Dict[str, List[str]], -) -> wcc.Tabs: - - all_dates, all_phases, all_wells, all_realizations = [], [], [], [] - for ens_name in ensemble_names: - all_dates.extend(dates[ens_name]) - all_phases.extend(phases[ens_name]) - all_wells.extend(wells[ens_name]) - all_realizations.extend(realizations[ens_name]) - all_dates = list(sorted(set(all_dates))) - all_phases = list(sorted(set(all_phases))) - all_wells = list(sorted(set(all_wells))) - all_realizations = list(sorted(set(all_realizations))) - all_well_collection_names = [] - for collection_name in well_collections.keys(): - all_well_collection_names.append(collection_name) - - return wcc.Tabs( - style=LayoutElements.TABS_STYLES, - children=[ - wcc.Tab( - label="Production misfit per real", - style=LayoutElements.TAB_STYLE, - selected_style=LayoutElements.TAB_SELECTED_STYLE, - children=_misfit_per_real_layout( - get_uuid, - ensemble_names, - all_dates, - all_phases, - all_wells, - all_well_collection_names, - all_realizations, - ), - ), - wcc.Tab( - label="Well production coverage", - style=LayoutElements.TAB_STYLE, - selected_style=LayoutElements.TAB_SELECTED_STYLE, - children=_well_prod_coverage_layout( - get_uuid, - ensemble_names, - all_dates, - all_phases, - all_wells, - all_well_collection_names, - all_realizations, - ), - ), - wcc.Tab( - label="Well production heatmap", - style=LayoutElements.TAB_STYLE, - selected_style=LayoutElements.TAB_SELECTED_STYLE, - children=_heatmap_layout( - get_uuid, - ensemble_names, - all_dates, - all_phases, - all_wells, - all_well_collection_names, - all_realizations, - ), - ), - ], - ) - - -# --- layout functions --- -def _misfit_per_real_layout( - get_uuid: Callable, - ensemble_names: List[str], - dates: List[datetime], - phases: List[str], - wells: List[str], - all_well_collection_names: List[str], - realizations: List[int], -) -> list: - children = [ - wcc.FlexBox( - id=get_uuid(LayoutElements.PROD_MISFIT_LAYOUT), - children=[ - wcc.Frame( - style={ - "flex": 1, - "height": "85vh", - "maxWidth": "200px", - }, - children=[ - wcc.Selectors( - label="Case settings", - children=[ - wcc.Dropdown( - label="Ensemble selector", - id=get_uuid( - LayoutElements.PROD_MISFIT_ENSEMBLE_NAMES - ), - options=[ - {"label": ens, "value": ens} - for ens in ensemble_names - ], - value=ensemble_names[:2], - multi=True, - clearable=False, - persistence=True, - persistence_type="memory", - ), - ], - ), - wcc.Selectors( - label="Filter settings", - children=[ - wcc.SelectWithLabel( - label="Date selector", - id=get_uuid(LayoutElements.PROD_MISFIT_DATES), - options=[ - { - "label": _date.strftime("%Y-%m-%d"), - "value": str(_date), - } - for _date in dates - ], - value=[str(dates[-1])], - size=min([len(dates), 5]), - ), - wcc.SelectWithLabel( - label="Phase selector", - id=get_uuid(LayoutElements.PROD_MISFIT_PHASES), - options=[ - {"label": phase, "value": phase} - for phase in phases - ], - value=phases, - size=min([len(phases), 3]), - ), - wcc.SelectWithLabel( - label="Well selector", - id=get_uuid(LayoutElements.PROD_MISFIT_WELL_NAMES), - options=[ - {"label": well, "value": well} for well in wells - ], - value=wells, - size=min([len(wells), 9]), - ), - wcc.RadioItems( - label="Combine wells and collections as", - id=get_uuid( - LayoutElements.PROD_MISFIT_WELL_COMBINE_TYPE - ), - options=[ - { - "label": "Intersection", - "value": "intersection", - }, - {"label": "Union", "value": "union"}, - ], - value="intersection", - ), - wcc.SelectWithLabel( - label="Well collection selector", - id=get_uuid( - LayoutElements.PROD_MISFIT_WELL_COLLECTIONS - ), - options=[ - {"label": collection, "value": collection} - for collection in all_well_collection_names - ], - value=all_well_collection_names, - size=min([len(wells), 5]), - ), - wcc.SelectWithLabel( - label="Realization selector", - id=get_uuid( - LayoutElements.PROD_MISFIT_REALIZATIONS - ), - options=[ - {"label": real, "value": real} - for real in realizations - ], - value=realizations, - size=min([len(wells), 5]), - ), - ], - ), - wcc.Selectors( - label="Plot settings and layout", - open_details=True, - children=[ - wcc.Dropdown( - label="Colorby", - id=get_uuid(LayoutElements.PROD_MISFIT_COLORBY), - options=[ - { - "label": "Total misfit", - "value": "misfit", - }, - {"label": "Phases", "value": "phases"}, - {"label": "Date", "value": "date"}, - {"label": "None", "value": None}, - ], - value="phases", - multi=False, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Sorting/ranking", - id=get_uuid(LayoutElements.PROD_MISFIT_SORTING), - options=[ - { - "label": "None", - "value": None, - }, - { - "label": "Ascending", - "value": "total ascending", - }, - { - "label": "Descending", - "value": "total descending", - }, - ], - value="total ascending", - multi=False, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Fig layout - height", - id=get_uuid(LayoutElements.PROD_MISFIT_FIGHEIGHT), - options=[ - { - "label": "Very small", - "value": 250, - }, - { - "label": "Small", - "value": 350, - }, - { - "label": "Medium", - "value": 450, - }, - { - "label": "Large", - "value": 700, - }, - { - "label": "Very large", - "value": 1000, - }, - ], - value=450, - clearable=False, - persistence=True, - persistence_type="memory", - ), - ], - ), - wcc.Selectors( - label="Misfit options", - open_details=True, # False, - children=[ - wcc.Dropdown( - label="Misfit weight", - id=get_uuid( - LayoutElements.PROD_MISFIT_OBS_ERROR_WEIGHT - ), - options=[ - { - "label": "Phase weights", - "value": -1.0, - }, - {"label": "None", "value": 0.0}, - { - "label": "10% obs error (min=1000)", - "value": 0.10, - }, - { - "label": "20% obs error (min=1000)", - "value": 0.20, - }, - ], - value=-1.0, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Misfit exponent", - id=get_uuid(LayoutElements.PROD_MISFIT_EXPONENT), - options=[ - { - "label": "Linear sum", - "value": 1.0, - }, - { - "label": "Squared sum", - "value": 2.0, - }, - ], - value=1.0, - clearable=False, - persistence=True, - persistence_type="memory", - ), - # wcc.Dropdown( - # label="Misfit normalization", - # id=get_uuid( - # LayoutElements.PROD_MISFIT_NORMALIZATION - # ), - # options=[ - # { - # "label": "Yes", - # "value": True, - # }, - # { - # "label": "No", - # "value": False, - # }, - # ], - # value=False, - # clearable=False, - # persistence=True, - # persistence_type="memory", - # ), - ], - ), - ], - ), - wcc.Frame( - style={"flex": 4, "minWidth": "500px"}, - children=[html.Div(id=get_uuid(LayoutElements.PROD_MISFIT_GRAPH))], - ), - ], - ), - ] - return children - - -def _well_prod_coverage_layout( - get_uuid: Callable, - ensemble_names: List[str], - dates: List[datetime], - phases: List[str], - wells: List[str], - all_well_collection_names: List[str], - realizations: List[int], -) -> list: - children = [ - wcc.FlexBox( - id=get_uuid(LayoutElements.WELL_COVERAGE_LAYOUT), - children=[ - wcc.Frame( - style={ - "flex": 1, - "height": "85vh", - "maxWidth": "200px", - }, - children=[ - wcc.Selectors( - label="Case settings", - children=[ - wcc.Dropdown( - label="Ensemble selector", - id=get_uuid( - LayoutElements.WELL_COVERAGE_ENSEMBLE_NAMES - ), - options=[ - {"label": ens, "value": ens} - for ens in ensemble_names - ], - value=ensemble_names[0:1], - multi=True, - clearable=False, - persistence=True, - persistence_type="memory", - ), - ], - ), - wcc.Selectors( - label="Filter settings - dates and phases", - children=[ - wcc.SelectWithLabel( - label="Date selector", - id=get_uuid(LayoutElements.WELL_COVERAGE_DATES), - options=[ - { - "label": _date.strftime("%Y-%m-%d"), - "value": str(_date), - } - for _date in dates - ], - value=[str(dates[-1])], - size=min([len(dates), 5]), - ), - wcc.SelectWithLabel( - label="Phase selector", - id=get_uuid(LayoutElements.WELL_COVERAGE_PHASES), - options=[ - {"label": phase, "value": phase} - for phase in phases - ], - value=phases, - size=min([len(phases), 3]), - ), - ], - ), - wcc.Selectors( - label="Filter settings - wells", - children=[ - wcc.SelectWithLabel( - label="Well selector", - id=get_uuid( - LayoutElements.WELL_COVERAGE_WELL_NAMES - ), - options=[ - {"label": well, "value": well} for well in wells - ], - value=wells, - size=min([len(wells), 9]), - ), - wcc.RadioItems( - label="Combine wells and collections as", - id=get_uuid( - LayoutElements.WELL_COVERAGE_WELL_COMBINE_TYPE - ), - options=[ - { - "label": "Intersection", - "value": "intersection", - }, - {"label": "Union", "value": "union"}, - ], - value="intersection", - ), - wcc.SelectWithLabel( - label="Well collection selector", - id=get_uuid( - LayoutElements.WELL_COVERAGE_WELL_COLLECTIONS - ), - options=[ - {"label": collection, "value": collection} - for collection in all_well_collection_names - ], - value=all_well_collection_names, - size=min([len(wells), 5]), - ), - ], - ), - wcc.Selectors( - label="Filter settings - realizations", - open_details=False, - children=[ - wcc.SelectWithLabel( - label="Realization selector", - id=get_uuid( - LayoutElements.WELL_COVERAGE_REALIZATIONS - ), - options=[ - {"label": real, "value": real} - for real in realizations - ], - value=realizations, - size=min([len(wells), 5]), - ), - ], - ), - wcc.Selectors( - label="Plot settings and layout", - open_details=True, - children=[ - wcc.Dropdown( - label="Colorby", - id=get_uuid(LayoutElements.WELL_COVERAGE_COLORBY), - options=[ - { - "label": "Ensemble", - "value": "ENSEMBLE", - }, - # {"label": "Well", "value": "WELL"}, - {"label": "Date", "value": "DATE"}, - ], - value="ENSEMBLE", - multi=False, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Plot type", - id=get_uuid(LayoutElements.WELL_COVERAGE_PLOT_TYPE), - options=[ - {"label": "Diff plot", "value": "diffplot"}, - { - "label": "Diff plot relative (%)", - "value": "rel_diffplot", - }, - {"label": "Cross plot", "value": "crossplot"}, - ], - value="diffplot", - multi=False, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Fig layout - height", - id=get_uuid(LayoutElements.WELL_COVERAGE_FIGHEIGHT), - options=[ - { - "label": "Very small", - "value": 250, - }, - { - "label": "Small", - "value": 350, - }, - { - "label": "Medium", - "value": 450, - }, - { - "label": "Large", - "value": 700, - }, - { - "label": "Very large", - "value": 1000, - }, - ], - value=450, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.RadioItems( - label="Colorby grouping", - id=get_uuid(LayoutElements.WELL_COVERAGE_BOXMODE), - options=[ - {"label": "Side by side", "value": "group"}, - {"label": "Overlay", "value": "overlay"}, - ], - value="group", - ), - wcc.RadioItems( - label="Show points", - id=get_uuid( - LayoutElements.WELL_COVERAGE_BOXPLOT_POINTS - ), - options=[ - {"label": "Outliers only", "value": "outliers"}, - {"label": "All points", "value": "all"}, - { - "label": "All points, no box", - "value": "strip", - }, - ], - value="outliers", - ), - ], - ), - ], - ), - wcc.Frame( - style={"flex": 4, "minWidth": "500px"}, - children=[ - html.Div(id=get_uuid(LayoutElements.WELL_COVERAGE_GRAPH)) - ], - ), - ], - ), - ] - return children - - -# def _group_prod_coverage() -> list: -# children = [ -# wcc.FlexBox( -# id=get_uuid(LayoutElements.group_coverage-layout), -# children=[ -# wcc.Frame( -# style={ -# "flex": 1, -# "height": "55vh", -# "maxWidth": "200px", -# }, -# children=[ -# wcc.Dropdown( -# label="Ensemble selector", -# id=get_uuid(LayoutElements.group_coverage-ensemble_names), -# options=[{"label": ens, "value": ens} for ens in ensembles], -# value=ensembles, -# multi=True, -# clearable=False, -# persistence=True, -# persistence_type="memory", -# ), -# wcc.SelectWithLabel( -# label="Date selector", -# id=get_uuid(LayoutElements.group_coverage-dates), -# options=[ -# {"label": _date, "value": _date} for _date in dates -# ], -# value=[dates[-1]], -# size=min([len(dates), 5]), -# ), -# wcc.SelectWithLabel( -# label="Phase selector", -# id=get_uuid(LayoutElements.group_coverage-phases), -# options=[ -# {"label": phase, "value": phase} for phase in phases -# ], -# value=phases, -# size=min([len(phases), 3]), -# ), -# wcc.SelectWithLabel( -# label="Group selector", -# id=get_uuid(LayoutElements.group_coverage-group_names), -# options=[ -# {"label": group, "value": group} for group in groups -# ], -# value=groups, -# size=min([len(groups), 9]), -# ), -# wcc.Dropdown( -# label="Colorby", -# id=get_uuid(LayoutElements.group_coverage-colorby), -# options=[ -# { -# "label": "Ensemble", -# "value": "ENSEMBLE", -# }, -# {"label": "Group", "value": "WELL"}, -# {"label": "Date", "value": "DATE"}, -# ], -# value="ENSEMBLE", -# multi=False, -# clearable=False, -# persistence=True, -# persistence_type="memory", -# ), -# wcc.Dropdown( -# label="Plot type", -# id=get_uuid(LayoutElements.group_coverage-plot_type), -# options=[ -# {"label": "Diff plot", "value": "diffplot"}, -# {"label": "Cross plot", "value": "crossplot"}, -# ], -# value="crossplot", -# multi=False, -# clearable=False, -# persistence=True, -# persistence_type="memory", -# ), -# ], -# ), -# wcc.Frame( -# style={"flex": 4, "minWidth": "500px"}, -# children=[html.Div(id=get_uuid(LayoutElements.group_coverage-graph))], -# ), -# ], -# ), -# ] -# return children - - -def _heatmap_layout( - get_uuid: Callable, - ensemble_names: List[str], - dates: List[datetime], - phases: List[str], - wells: List[str], - all_well_collection_names: List[str], - realizations: List[int], -) -> list: - children = [ - wcc.FlexBox( - id=get_uuid(LayoutElements.HEATMAP_LAYOUT), - children=[ - wcc.Frame( - style={ - "flex": 1, - "height": "80vh", - "maxWidth": "200px", - }, - children=[ - wcc.Selectors( - label="Case settings", - children=[ - wcc.Dropdown( - label="Ensemble selector", - id=get_uuid(LayoutElements.HEATMAP_ENSEMBLE_NAMES), - options=[ - {"label": ens, "value": ens} - for ens in ensemble_names - ], - value=ensemble_names[0:1], - multi=True, - clearable=False, - persistence=True, - persistence_type="memory", - ), - ], - ), - wcc.Selectors( - label="Filter settings - dates and phases", - children=[ - wcc.SelectWithLabel( - label="Date selector", - id=get_uuid(LayoutElements.HEATMAP_DATES), - options=[ - { - "label": _date.strftime("%Y-%m-%d"), - "value": str(_date), - } - for _date in dates - ], - value=[str(_date) for _date in dates], - size=min([len(dates), 5]), - ), - wcc.SelectWithLabel( - label="Phase selector", - id=get_uuid(LayoutElements.HEATMAP_PHASES), - options=[ - {"label": phase, "value": phase} - for phase in phases - ], - value=phases, - size=min([len(phases), 3]), - ), - ], - ), - wcc.Selectors( - label="Filter settings - wells", - children=[ - wcc.SelectWithLabel( - label="Well selector", - id=get_uuid(LayoutElements.HEATMAP_WELL_NAMES), - options=[ - {"label": well, "value": well} for well in wells - ], - value=wells, - size=min([len(wells), 9]), - ), - wcc.RadioItems( - label="Combine wells and collections as", - id=get_uuid( - LayoutElements.HEATMAP_WELL_COMBINE_TYPE - ), - options=[ - { - "label": "Intersection", - "value": "intersection", - }, - {"label": "Union", "value": "union"}, - ], - value="intersection", - ), - wcc.SelectWithLabel( - label="Well collection selector", - id=get_uuid( - LayoutElements.HEATMAP_WELL_COLLECTIONS - ), - options=[ - { - "label": collection, - "value": collection, - } - for collection in all_well_collection_names - ], - value=all_well_collection_names, - size=min([len(wells), 5]), - ), - ], - ), - wcc.Selectors( - label="Filter settings - realizations", - open_details=False, - children=[ - wcc.SelectWithLabel( - label="Realization selector", - id=get_uuid(LayoutElements.HEATMAP_REALIZATIONS), - options=[ - {"label": real, "value": real} - for real in realizations - ], - value=realizations, - size=min([len(wells), 5]), - ), - ], - ), - wcc.Dropdown( - label="Show wells with largest misfit", - id=get_uuid(LayoutElements.HEATMAP_FILTER_LARGEST), - options=[ - {"label": "Show all", "value": 0}, - {"label": "2", "value": 2}, - {"label": "4", "value": 4}, - {"label": "6", "value": 6}, - {"label": "8", "value": 8}, - {"label": "10", "value": 10}, - {"label": "12", "value": 12}, - {"label": "15", "value": 15}, - {"label": "20", "value": 20}, - {"label": "25", "value": 25}, - ], - value=0, - multi=False, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Selectors( - label="Plot settings and layout", - open_details=True, - children=[ - wcc.Dropdown( - label="Plot type", - id=get_uuid(LayoutElements.HEATMAP_PLOT_TYPE), - options=[ - {"label": "Mean misfit", "value": "diffplot"}, - { - "label": "Mean misfit relative (%)", - "value": "rel_diffplot", - }, - ], - value="diffplot", - multi=False, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Fig layout - height", - id=get_uuid(LayoutElements.HEATMAP_FIGHEIGHT), - options=[ - { - "label": "Very small", - "value": 250, - }, - { - "label": "Small", - "value": 350, - }, - { - "label": "Medium", - "value": 450, - }, - { - "label": "Large", - "value": 700, - }, - { - "label": "Very large", - "value": 1000, - }, - ], - value=450, - clearable=False, - persistence=True, - persistence_type="memory", - ), - wcc.Dropdown( - label="Color range scaling (relative to max)", - id=get_uuid(LayoutElements.HEATMAP_SCALE_COL_RANGE), - options=[ - {"label": f"{x:.0%}", "value": x} - for x in [ - 0.1, - 0.2, - 0.3, - 0.4, - 0.5, - 0.6, - 0.7, - 0.8, - 0.9, - 1.0, - 1.5, - 2.0, - ] - ], - style={"display": "block"}, - value=1.0, - clearable=False, - persistence=True, - persistence_type="memory", - ), - ], - ), - ], - ), - wcc.Frame( - style={"flex": 4, "minWidth": "500px"}, - children=[html.Div(id=get_uuid(LayoutElements.HEATMAP_GRAPH))], - ), - ], - ), - ] - return children diff --git a/webviz_subsurface/plugins/_prod_misfit/_plugin.py b/webviz_subsurface/plugins/_prod_misfit/_plugin.py index c41fe7e56..df7ded2ff 100644 --- a/webviz_subsurface/plugins/_prod_misfit/_plugin.py +++ b/webviz_subsurface/plugins/_prod_misfit/_plugin.py @@ -3,8 +3,6 @@ from pathlib import Path from typing import Callable, Dict, List, Optional, Tuple -import dash -import webviz_core_components as wcc from webviz_config import WebvizPluginABC, WebvizSettings from webviz_subsurface._models import WellAttributesModel @@ -13,11 +11,25 @@ from .._simulation_time_series._utils.create_provider_set_from_paths import ( create_presampled_provider_set_from_paths, ) -from ._callbacks import plugin_callbacks -from ._layout import main_layout + +# from .._simulation_time_series.types.provider_set import ( +# create_presampled_provider_set_from_paths, +# ) +from ._plugin_ids import PluginIds +from .shared_settings import Filter +from .views import ( + MisfitOptions, + MisfitPerRealView, + PlotSettingsCoverage, + PlotSettingsHeatmap, + PlotSettingsMisfit, + ProdCoverageView, + ProdHeatmapView, +) class ProdMisfit(WebvizPluginABC): + # pylint: disable=too-many-instance-attributes """Visualizes production data misfit at selected date(s). When not dealing with absolute value of differences, difference plots are @@ -84,7 +96,6 @@ class ProdMisfit(WebvizPluginABC): def __init__( self, - app: dash.Dash, webviz_settings: WebvizSettings, ensembles: list, rel_file_pattern: str = "share/results/unsmry/*.arrow", @@ -95,6 +106,7 @@ def __init__( excl_name_contains: list = None, phase_weights: dict = None, ): + # pylint: disable=too-many-statements super().__init__() @@ -171,21 +183,211 @@ def __init__( self.well_collections = _get_well_collections_from_attr( self.wells, self._well_attributes ) + # ------------------------------------------------------------ + # calculations for settings + self.all_dates, self.all_phases, self.all_wells, self.all_realizations = ( + [], + [], + [], + [], + ) + # pylint: disable=consider-iterating-dictionary + for ens_name in self.ensemble_names: + self.all_dates.extend(self.dates[ens_name]) + self.all_phases.extend(self.phases[ens_name]) + self.all_wells.extend(self.wells[ens_name]) + self.all_realizations.extend(self.realizations[ens_name]) + self.all_dates = list(sorted(set(self.all_dates))) + self.all_phases = list(sorted(set(self.all_phases))) + self.all_wells = list(sorted(set(self.all_wells))) + self.all_realizations = list(sorted(set(self.all_realizations))) + self.all_well_collection_names = [] + for collection_name in self.well_collections.keys(): + self.all_well_collection_names.append(collection_name) + # -------------------------------------------------------------- + # add views, settings and stores + + self.add_store( + PluginIds.Stores.SELECTED_ENSEMBLES, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PluginIds.Stores.SELECTED_DATES, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PluginIds.Stores.SELECTED_PHASE, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PluginIds.Stores.SELECTED_WELLS, WebvizPluginABC.StorageType.SESSION + ) + self.add_store( + PluginIds.Stores.SELECTED_COMBINE_WELLS_COLLECTION, + WebvizPluginABC.StorageType.SESSION, + ) + self.add_store( + PluginIds.Stores.SELECTED_WELL_COLLECTIONS, + WebvizPluginABC.StorageType.SESSION, + ) + self.add_store( + PluginIds.Stores.SELECTED_REALIZATIONS, WebvizPluginABC.StorageType.SESSION + ) - self.set_callbacks(app) + self.add_shared_settings_group( + Filter( + self.ensemble_names, + self.all_dates, + self.all_phases, + self.all_wells, + self.all_realizations, + self.all_well_collection_names, + ), + PluginIds.SharedSettings.FILTER, + ) - @property - def layout(self) -> wcc.Tabs: - return main_layout( - get_uuid=self.uuid, - ensemble_names=self.ensemble_names, - dates=self.dates, - phases=self.phases, - wells=self.wells, - realizations=self.realizations, - well_collections=self.well_collections, + self.add_view( + MisfitPerRealView( + input_provider_set=self._input_provider_set, + ens_vectors=self.vectors, + ens_realizations=self.realizations, + well_collections=self.well_collections, + weight_reduction_factor_oil=self.weight_reduction_factor_oil, + weight_reduction_factor_wat=self.weight_reduction_factor_wat, + weight_reduction_factor_gas=self.weight_reduction_factor_gas, + ), + PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL, + ) + self.add_view( + ProdCoverageView( + input_provider_set=self._input_provider_set, + ens_vectors=self.vectors, + ens_realizations=self.realizations, + well_collections=self.well_collections, + ), + PluginIds.MisfitViews.WELL_PRODUCTION_COVERAGE, + ) + self.add_view( + ProdHeatmapView( + input_provider_set=self._input_provider_set, + ens_vectors=self.vectors, + ens_realizations=self.realizations, + well_collections=self.well_collections, + ), + PluginIds.MisfitViews.WELL_PRODUCTION_HEATMAP, ) + @property + def tour_steps(self) -> List[dict]: + return [ + { + "id": self.view(PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL) + .layout_element(MisfitPerRealView.Ids.MAIN_COLUMN) + .get_unique_id(), + "content": """Shows production misfit per realization. + Several ensembles can be shown at the same time.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.ENSEMBLE_SELECTOR), + "content": """Select which ensembles to view graphs of.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.DATE_SELECTOR), + "content": """Choose a single or several dates.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.PHASE_SELECTOR), + "content": """Select what phases to be shown in the plot.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.WELL_SELECTOR), + "content": """Select what wells to include in the data.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.COMBINE_WELL_AND_COLLECTION_AS), + "content": """Combine the well and collection data as union or intersection.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.WELL_COLLECTION_SELECTOR), + "content": """Choose what collection data to include.""", + }, + { + "id": self.shared_settings_group( + PluginIds.SharedSettings.FILTER + ).component_unique_id(Filter.Ids.REALIZATION_SELECTOR), + "content": """Choose how many realizations to include in the plot.""", + }, + { + "id": self.view(PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL) + .settings_group(MisfitPerRealView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsMisfit.Ids.COLORBY), + "content": """Color plot by phases, date or total misfit.""", + }, + { + "id": self.view(PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL) + .settings_group(MisfitPerRealView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsMisfit.Ids.SORTING_RANKING), + "content": """Rank data by ascending or descending value.""", + }, + { + "id": self.view(PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL) + .settings_group(MisfitPerRealView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsMisfit.Ids.FIG_LAYOUT_HEIGHT), + "content": """Select the size of the plot.""", + }, + { + "id": self.view(PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL) + .settings_group(MisfitPerRealView.Ids.MISFIT_OPTIONS) + .component_unique_id(MisfitOptions.Ids.MISFIT_WEIGHT), + "content": """Select how to weigh misfits.""", + }, + { + "id": self.view(PluginIds.MisfitViews.PRODUCTION_MISFIT_PER_REAL) + .settings_group(MisfitPerRealView.Ids.MISFIT_OPTIONS) + .component_unique_id(MisfitOptions.Ids.MISFIT_EXPONENT), + "content": """Choose between linear or square sum of misfit exponent.""", + }, + { + "id": self.view(PluginIds.MisfitViews.WELL_PRODUCTION_COVERAGE) + .layout_element(ProdCoverageView.Ids.MAIN_COLUMN) + .get_unique_id(), + "content": """Shows well production coverage in a crossplot.""", + }, + { + "id": self.view(PluginIds.MisfitViews.WELL_PRODUCTION_COVERAGE) + .settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.COLORBY_GROUPING), + "content": """Choose to have the ensembles in the plot overlay or side by side.""", + }, + { + "id": self.view(PluginIds.MisfitViews.WELL_PRODUCTION_COVERAGE) + .settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.SHOW_POINTS), + "content": """Select how many points to show.""", + }, + { + "id": self.view(PluginIds.MisfitViews.WELL_PRODUCTION_HEATMAP) + .layout_element(ProdHeatmapView.Ids.MAIN_COLUMN) + .get_unique_id(), + "content": """Shows cummulative misfit in heatmap.""", + }, + { + "id": self.view(PluginIds.MisfitViews.WELL_PRODUCTION_HEATMAP) + .settings_group(ProdHeatmapView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsHeatmap.Ids.COLOR_RANGE_SCALING), + "content": """Select the scale of the color range relative to max.""", + }, + ] + def add_webvizstore(self) -> List[Tuple[Callable, List[Dict]]]: return ( [self._well_attributes.webviz_store] @@ -194,18 +396,6 @@ def add_webvizstore(self) -> List[Tuple[Callable, List[Dict]]]: ) # --------------------------------------------- - def set_callbacks(self, app: dash.Dash) -> None: - plugin_callbacks( - app=app, - get_uuid=self.uuid, - input_provider_set=self._input_provider_set, - ens_vectors=self.vectors, - ens_realizations=self.realizations, - well_collections=self.well_collections, - weight_reduction_factor_oil=self.weight_reduction_factor_oil, - weight_reduction_factor_wat=self.weight_reduction_factor_wat, - weight_reduction_factor_gas=self.weight_reduction_factor_gas, - ) # ------------------------------------------------------------------------ diff --git a/webviz_subsurface/plugins/_prod_misfit/_plugin_ids.py b/webviz_subsurface/plugins/_prod_misfit/_plugin_ids.py new file mode 100644 index 000000000..2f0105ea2 --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/_plugin_ids.py @@ -0,0 +1,25 @@ +class PluginIds: + # pylint: disable=too-few-public-methods + class Stores: + # pylint: disable=too-few-public-methods + SELECTED_ENSEMBLES = "selected-ensembles" + SELECTED_DATES = "selected-dates" + SELECTED_PHASE = "selected-phase" + SELECTED_WELLS = "selected-wells" + SELECTED_COMBINE_WELLS_COLLECTION = "selected-combine-wells-collection" + SELECTED_WELL_COLLECTIONS = "selected-well-collections" + SELECTED_REALIZATIONS = "selected-realizations" + + SELECTED_FIG_LAYOUT_HEIGHT = "selected-fig-layout-height" + + class SharedSettings: + # pylint: disable=too-few-public-methods + FILTER = "filter" + + class MisfitViews: + # pylint: disable=too-few-public-methods + GROUP_NAME = "Misfit analysis" + + PRODUCTION_MISFIT_PER_REAL = "production-misfit-per-real" + WELL_PRODUCTION_COVERAGE = "well-production-coverage" + WELL_PRODUCTION_HEATMAP = "well-production-heatmap" diff --git a/webviz_subsurface/plugins/_prod_misfit/shared_settings/__init__.py b/webviz_subsurface/plugins/_prod_misfit/shared_settings/__init__.py new file mode 100644 index 000000000..4f054cbae --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/shared_settings/__init__.py @@ -0,0 +1 @@ +from ._filter import Filter diff --git a/webviz_subsurface/plugins/_prod_misfit/shared_settings/_filter.py b/webviz_subsurface/plugins/_prod_misfit/shared_settings/_filter.py new file mode 100644 index 000000000..9084af016 --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/shared_settings/_filter.py @@ -0,0 +1,199 @@ +from datetime import datetime +from typing import List + +import webviz_core_components as wcc +from dash import Input, Output, callback +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC + +from .._plugin_ids import PluginIds + + +class Filter(SettingsGroupABC): + class Ids: + # pylint: disable=too-few-public-methods + ENSEMBLE_SELECTOR = "ensemble-selector" + DATE_SELECTOR = "date-selector" + PHASE_SELECTOR = "phase-selector" + WELL_SELECTOR = "well-selector" + COMBINE_WELL_AND_COLLECTION_AS = "combine-well-and-collection-as" + WELL_COLLECTION_SELECTOR = "well-collection-selector" + REALIZATION_SELECTOR = "realization-selector" + + FIG_LAYOUT_HEIGHT = "fig-layout-height" + + def __init__( + self, + ensemble_names: List[str], + dates: List[datetime], + phases: List[str], + wells: List[str], + realizations: List[int], + all_well_collection_names: List[str], + ) -> None: + super().__init__("Filter") + + self.ensemble_names = ensemble_names + self.dates = dates + self.phases = phases + self.wells = wells + self.realizations = realizations + self.all_well_collection_names = all_well_collection_names + + def layout(self) -> List[Component]: + return [ + wcc.Checklist( + label="Ensemble selector", + id=self.register_component_unique_id(Filter.Ids.ENSEMBLE_SELECTOR), + options=[{"label": ens, "value": ens} for ens in self.ensemble_names], + value=self.ensemble_names[:2], + vertical=False, + ), + wcc.SelectWithLabel( + label="Date selector", + id=self.register_component_unique_id(Filter.Ids.DATE_SELECTOR), + options=[ + { + "label": _date.strftime("%Y-%m-%d"), + "value": str(_date), + } + for _date in self.dates + ], + value=[str(self.dates[-1])], + size=min([len(self.dates), 5]), + ), + wcc.SelectWithLabel( + label="Phase selector", + id=self.register_component_unique_id(Filter.Ids.PHASE_SELECTOR), + options=[{"label": phase, "value": phase} for phase in self.phases], + value=self.phases, + size=min([len(self.phases), 3]), + ), + wcc.SelectWithLabel( + label="Well selector", + id=self.register_component_unique_id(Filter.Ids.WELL_SELECTOR), + options=[{"label": well, "value": well} for well in self.wells], + value=self.wells, + size=min([len(self.wells), 9]), + ), + wcc.RadioItems( + label="Combine wells and collections as", + id=self.register_component_unique_id( + Filter.Ids.COMBINE_WELL_AND_COLLECTION_AS + ), + options=[ + { + "label": "Intersection", + "value": "intersection", + }, + {"label": "Union", "value": "union"}, + ], + value="intersection", + ), + wcc.SelectWithLabel( + label="Well collection selector", + id=self.register_component_unique_id( + Filter.Ids.WELL_COLLECTION_SELECTOR + ), + options=[ + {"label": collection, "value": collection} + for collection in self.all_well_collection_names + ], + value=self.all_well_collection_names, + size=min([len(self.all_well_collection_names), 5]), + ), + wcc.SelectWithLabel( + label="Realization selector", + id=self.register_component_unique_id(Filter.Ids.REALIZATION_SELECTOR), + options=[{"label": real, "value": real} for real in self.realizations], + value=self.realizations, + size=min([len(self.wells), 5]), + ), + ] + + def set_callbacks(self) -> None: + @callback( + Output( + self.get_store_unique_id(PluginIds.Stores.SELECTED_ENSEMBLES), "data" + ), + Input( + self.component_unique_id(Filter.Ids.ENSEMBLE_SELECTOR).to_string(), + "value", + ), + ) + def _set_ensembles(ensembles: List[str]) -> List[str]: + return ensembles + + @callback( + Output(self.get_store_unique_id(PluginIds.Stores.SELECTED_DATES), "data"), + Input( + self.component_unique_id(Filter.Ids.DATE_SELECTOR).to_string(), + "value", + ), + ) + def _set_dates(dates: List[datetime]) -> List[datetime]: + return dates + + @callback( + Output(self.get_store_unique_id(PluginIds.Stores.SELECTED_PHASE), "data"), + Input( + self.component_unique_id(Filter.Ids.PHASE_SELECTOR).to_string(), + "value", + ), + ) + def _set_phase(phase: List[str]) -> List[str]: + return phase + + @callback( + Output(self.get_store_unique_id(PluginIds.Stores.SELECTED_WELLS), "data"), + Input( + self.component_unique_id(Filter.Ids.WELL_SELECTOR).to_string(), + "value", + ), + ) + def _set_wells(wells: List[str]) -> List[str]: + return wells + + @callback( + Output( + self.get_store_unique_id( + PluginIds.Stores.SELECTED_COMBINE_WELLS_COLLECTION + ), + "data", + ), + Input( + self.component_unique_id( + Filter.Ids.COMBINE_WELL_AND_COLLECTION_AS + ).to_string(), + "value", + ), + ) + def _set_combine_well_and_collection(combine_well_and_collection: str) -> str: + return combine_well_and_collection + + @callback( + Output( + self.get_store_unique_id(PluginIds.Stores.SELECTED_WELL_COLLECTIONS), + "data", + ), + Input( + self.component_unique_id( + Filter.Ids.WELL_COLLECTION_SELECTOR + ).to_string(), + "value", + ), + ) + def _set_well_collection_selector(well_collection: List[str]) -> List[str]: + return well_collection + + @callback( + Output( + self.get_store_unique_id(PluginIds.Stores.SELECTED_REALIZATIONS), "data" + ), + Input( + self.component_unique_id(Filter.Ids.REALIZATION_SELECTOR).to_string(), + "value", + ), + ) + def _set_realizations(realizations: List[int]) -> List[int]: + return realizations diff --git a/webviz_subsurface/plugins/_prod_misfit/view_elements/__init__.py b/webviz_subsurface/plugins/_prod_misfit/view_elements/__init__.py new file mode 100644 index 000000000..d4648650f --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/view_elements/__init__.py @@ -0,0 +1 @@ +from ._graph import Graph diff --git a/webviz_subsurface/plugins/_prod_misfit/view_elements/_graph.py b/webviz_subsurface/plugins/_prod_misfit/view_elements/_graph.py new file mode 100644 index 000000000..996c86eac --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/view_elements/_graph.py @@ -0,0 +1,22 @@ +from typing import Type + +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import ViewElementABC +from webviz_core_components import Graph as WccGraph + + +class Graph(ViewElementABC): + class Ids: + # pylint: disable=too-few-public-methods + GRAPH = "graph" + + def __init__(self, height: str = "65vh") -> None: + super().__init__() + + self.height = height + + def inner_layout(self) -> Type[Component]: + return WccGraph( + id=self.register_component_unique_id(Graph.Ids.GRAPH), + style={"height": self.height, "min-height": "300px"}, + ) diff --git a/webviz_subsurface/plugins/_prod_misfit/views/__init__.py b/webviz_subsurface/plugins/_prod_misfit/views/__init__.py new file mode 100644 index 000000000..81efd41ff --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/views/__init__.py @@ -0,0 +1,7 @@ +from ._production_misfit_per_real import ( + MisfitOptions, + MisfitPerRealView, + PlotSettingsMisfit, +) +from ._well_production_coverage import PlotSettingsCoverage, ProdCoverageView +from ._well_production_heatmap import PlotSettingsHeatmap, ProdHeatmapView diff --git a/webviz_subsurface/plugins/_prod_misfit/views/_production_misfit_per_real.py b/webviz_subsurface/plugins/_prod_misfit/views/_production_misfit_per_real.py new file mode 100644 index 000000000..03bbb1a0b --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/views/_production_misfit_per_real.py @@ -0,0 +1,309 @@ +from typing import Dict, List, Union + +import webviz_core_components as wcc +from dash import Input, Output, callback +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC, ViewABC + +from ..._simulation_time_series._views._subplot_view._utils.provider_set import ( + ProviderSet, +) + +# from ..._simulation_time_series._utils.provider_set import ProviderSet +from .._plugin_ids import PluginIds +from ..utils import make_dataframes as makedf +from ..utils import make_figures as makefigs +from ._view_functions import _get_well_names_combined + + +class PlotSettingsMisfit(SettingsGroupABC): + class Ids: + # pylint: disable=too-few-public-methods + COLORBY = "colorby" + SORTING_RANKING = "sorting-ranking" + FIG_LAYOUT_HEIGHT = "fig-layout-height" + + def __init__(self) -> None: + super().__init__("Plot Settings") + + def layout(self) -> List[Component]: + return [ + wcc.Dropdown( + label="Color by", + id=self.register_component_unique_id(PlotSettingsMisfit.Ids.COLORBY), + options=[ + { + "label": "Total misfit", + "value": "misfit", + }, + {"label": "Phases", "value": "phases"}, + {"label": "Date", "value": "date"}, + {"label": "None", "value": None}, + ], + value="phases", + multi=False, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Sorting/ranking", + id=self.register_component_unique_id( + PlotSettingsMisfit.Ids.SORTING_RANKING + ), + options=[ + { + "label": "None", + "value": None, + }, + { + "label": "Ascending", + "value": "total ascending", + }, + { + "label": "Descending", + "value": "total descending", + }, + ], + value="total ascending", + multi=False, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Fig layout - height", + id=self.register_component_unique_id( + PlotSettingsMisfit.Ids.FIG_LAYOUT_HEIGHT + ), + options=[ + { + "label": "Very small", + "value": 250, + }, + { + "label": "Small", + "value": 350, + }, + { + "label": "Medium", + "value": 450, + }, + { + "label": "Large", + "value": 700, + }, + { + "label": "Very large", + "value": 1000, + }, + ], + value=450, + clearable=False, + persistence=True, + persistence_type="memory", + ), + ] + + +class MisfitOptions(SettingsGroupABC): + class Ids: + # pylint: disable=too-few-public-methods + MISFIT_WEIGHT = "misfit-weight" + MISFIT_EXPONENT = "misfit-exponent" + + def __init__(self) -> None: + super().__init__("Misfit options") + + def layout(self) -> List[Component]: + return [ + wcc.Dropdown( + label="Misfit weight", + id=self.register_component_unique_id(MisfitOptions.Ids.MISFIT_WEIGHT), + options=[ + { + "label": "Phase weights", + "value": -1.0, + }, + {"label": "None", "value": 0.0}, + { + "label": "10% obs error (min=1000)", + "value": 0.10, + }, + { + "label": "20% obs error (min=1000)", + "value": 0.20, + }, + ], + value=-1.0, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Misfit exponent", + id=self.register_component_unique_id(MisfitOptions.Ids.MISFIT_EXPONENT), + options=[ + { + "label": "Linear sum", + "value": 1.0, + }, + { + "label": "Squared sum", + "value": 2.0, + }, + ], + value=1.0, + clearable=False, + persistence=True, + persistence_type="memory", + ), + ] + + +class MisfitPerRealView(ViewABC): + class Ids: + # pylint: disable=too-few-public-methods + PLOT_SETTINGS = "plot-settings" + MISFIT_OPTIONS = "misfit-options" + MAIN_COLUMN = "main-column" + + # pylint: disable=too-many-arguments + def __init__( + self, + input_provider_set: ProviderSet, + ens_vectors: Dict[str, List[str]], + ens_realizations: Dict[str, List[int]], + well_collections: Dict[str, List[str]], + weight_reduction_factor_oil: float, + weight_reduction_factor_wat: float, + weight_reduction_factor_gas: float, + ) -> None: + super().__init__("Production misfit per real") + + self.input_provider_set = input_provider_set + self.ens_vectors = ens_vectors + self.ens_realizations = ens_realizations + self.well_collections = well_collections + self.weight_reduction_factor_oil = weight_reduction_factor_oil + self.weight_reduction_factor_wat = weight_reduction_factor_wat + self.weight_reduction_factor_gas = weight_reduction_factor_gas + + self.add_settings_group( + PlotSettingsMisfit(), MisfitPerRealView.Ids.PLOT_SETTINGS + ) + self.add_settings_group(MisfitOptions(), MisfitPerRealView.Ids.MISFIT_OPTIONS) + self.main_column = self.add_column(MisfitPerRealView.Ids.MAIN_COLUMN) + + def set_callbacks(self) -> None: + @callback( + Output( + self.layout_element(MisfitPerRealView.Ids.MAIN_COLUMN) + .get_unique_id() + .to_string(), + "children", + ), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_ENSEMBLES), "data" + ), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_DATES), "data"), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_PHASE), "data"), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_WELLS), "data"), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_WELL_COLLECTIONS), + "data", + ), + Input( + self.get_store_unique_id( + PluginIds.Stores.SELECTED_COMBINE_WELLS_COLLECTION + ), + "data", + ), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_REALIZATIONS), "data" + ), + Input( + self.settings_group(MisfitPerRealView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsMisfit.Ids.COLORBY) + .to_string(), + "value", + ), + Input( + self.settings_group(MisfitPerRealView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsMisfit.Ids.SORTING_RANKING) + .to_string(), + "value", + ), + Input( + self.settings_group(MisfitPerRealView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsMisfit.Ids.FIG_LAYOUT_HEIGHT) + .to_string(), + "value", + ), + Input( + self.settings_group(MisfitPerRealView.Ids.MISFIT_OPTIONS) + .component_unique_id(MisfitOptions.Ids.MISFIT_WEIGHT) + .to_string(), + "value", + ), + Input( + self.settings_group(MisfitPerRealView.Ids.MISFIT_OPTIONS) + .component_unique_id(MisfitOptions.Ids.MISFIT_EXPONENT) + .to_string(), + "value", + ), + ) + def _update_plots( + ensemble_names: List[str], + selector_dates: list, + selector_phases: list, + selector_well_names: list, + selector_well_collection_names: list, + selector_well_combine_type: str, + selector_realizations: List[int], + colorby: str, + sorting: str, + figheight: int, + obs_error_weight: float, + misfit_exponent: float, + ) -> Union[str, List[Component]]: + + if not ensemble_names: + return "No ensembles selected" + + well_names = _get_well_names_combined( + self.well_collections, + selector_well_collection_names, + selector_well_names, + selector_well_combine_type, + ) + + dframe = makedf.get_df_diff( + makedf.get_df_smry( + self.input_provider_set, + ensemble_names, + self.ens_vectors, + self.ens_realizations, + selector_realizations, + well_names, + selector_phases, + selector_dates, + ), + obs_error_weight, + self.weight_reduction_factor_oil, + self.weight_reduction_factor_wat, + self.weight_reduction_factor_gas, + misfit_exponent, + ) + + figures = makefigs.prod_misfit_plot( + dframe, + selector_phases, + colorby, + sorting, + figheight, + misfit_exponent, + # misfit_normalization, + ) + + return figures diff --git a/webviz_subsurface/plugins/_prod_misfit/views/_view_functions.py b/webviz_subsurface/plugins/_prod_misfit/views/_view_functions.py new file mode 100644 index 000000000..78ad24786 --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/views/_view_functions.py @@ -0,0 +1,27 @@ +from typing import Dict, List + + +def _get_well_names_combined( + well_collections: Dict[str, List[str]], + selected_collection_names: list, + selected_wells: list, + combine_type: str = "intersection", +) -> List[str]: + """Return union or intersection of well list and well collection lists.""" + + selected_collection_wells = [] + for collection_name in selected_collection_names: + selected_collection_wells.extend(well_collections[collection_name]) + selected_collection_wells = list(set(selected_collection_wells)) + if combine_type == "intersection": + # find intersection of selector wells and selector well collections + well_names_combined = [ + well for well in selected_wells if well in selected_collection_wells + ] + else: + # find union of selector wells and selector well collections + well_names_combined = list(selected_collection_wells) + well_names_combined.extend(selected_wells) + well_names_combined = list(set(well_names_combined)) + + return well_names_combined diff --git a/webviz_subsurface/plugins/_prod_misfit/views/_well_production_coverage.py b/webviz_subsurface/plugins/_prod_misfit/views/_well_production_coverage.py new file mode 100644 index 000000000..04d468abf --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/views/_well_production_coverage.py @@ -0,0 +1,285 @@ +from typing import Dict, List, Union + +import webviz_core_components as wcc +from dash import Input, Output, callback +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC, ViewABC + +from ..._simulation_time_series._views._subplot_view._utils.provider_set import ( + ProviderSet, +) + +# from ..._simulation_time_series.types.provider_set import ProviderSet +from .._plugin_ids import PluginIds +from ..utils import make_dataframes as makedf +from ..utils import make_figures as makefigs +from ._view_functions import _get_well_names_combined + + +class PlotSettingsCoverage(SettingsGroupABC): + class Ids: + # pylint: disable=too-few-public-methods + COLORBY = "colorby" + FIG_LAYOUT_HEIGHT = "fig-layout-height" + PLOT_TYPE = "plot-type" + COLORBY_GROUPING = "colorby-grouping" + SHOW_POINTS = "show-points" + + def __init__(self) -> None: + super().__init__("Plot Settings") + + def layout(self) -> List[Component]: + return [ + wcc.Dropdown( + label="Color by", + id=self.register_component_unique_id(PlotSettingsCoverage.Ids.COLORBY), + options=[ + { + "label": "Ensemble", + "value": "ENSEMBLE", + }, + # {"label": "Well", "value": "WELL"}, + {"label": "Date", "value": "DATE"}, + ], + value="ENSEMBLE", + multi=False, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Plot type", + id=self.register_component_unique_id( + PlotSettingsCoverage.Ids.PLOT_TYPE + ), + options=[ + {"label": "Diff plot", "value": "diffplot"}, + { + "label": "Diff plot relative (%)", + "value": "rel_diffplot", + }, + {"label": "Cross plot", "value": "crossplot"}, + ], + value="diffplot", + multi=False, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Fig layout - height", + id=self.register_component_unique_id( + PlotSettingsCoverage.Ids.FIG_LAYOUT_HEIGHT + ), + options=[ + { + "label": "Very small", + "value": 250, + }, + { + "label": "Small", + "value": 350, + }, + { + "label": "Medium", + "value": 450, + }, + { + "label": "Large", + "value": 700, + }, + { + "label": "Very large", + "value": 1000, + }, + ], + value=450, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.RadioItems( + label="Grouping", + id=self.register_component_unique_id( + PlotSettingsCoverage.Ids.COLORBY_GROUPING + ), + options=[ + {"label": "Side by side", "value": "group"}, + {"label": "Overlay", "value": "overlay"}, + ], + value="group", + ), + wcc.RadioItems( + label="Show points", + id=self.register_component_unique_id( + PlotSettingsCoverage.Ids.SHOW_POINTS + ), + options=[ + {"label": "Outliers only", "value": "outliers"}, + {"label": "All points", "value": "all"}, + { + "label": "All points, no box", + "value": "strip", + }, + ], + value="outliers", + ), + ] + + +class ProdCoverageView(ViewABC): + class Ids: + # pylint: disable=too-few-public-methods + PLOT_SETTINGS = "plot-settings" + MAIN_COLUMN = "main-column" + + # pylint: disable=too-many-arguments + # pylint: disable=too-many-locals + def __init__( + self, + input_provider_set: ProviderSet, + ens_vectors: Dict[str, List[str]], + ens_realizations: Dict[str, List[int]], + well_collections: Dict[str, List[str]], + ) -> None: + super().__init__("Well production coverage") + + self.input_provider_set = input_provider_set + self.ens_vectors = ens_vectors + self.ens_realizations = ens_realizations + self.well_collections = well_collections + + self.add_settings_group( + PlotSettingsCoverage(), ProdCoverageView.Ids.PLOT_SETTINGS + ) + self.main_column = self.add_column(ProdCoverageView.Ids.MAIN_COLUMN) + + def set_callbacks(self) -> None: + @callback( + Output( + self.layout_element(ProdCoverageView.Ids.MAIN_COLUMN) + .get_unique_id() + .to_string(), + "children", + ), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_ENSEMBLES), "data" + ), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_DATES), "data"), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_PHASE), "data"), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_WELLS), "data"), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_WELL_COLLECTIONS), + "data", + ), + Input( + self.get_store_unique_id( + PluginIds.Stores.SELECTED_COMBINE_WELLS_COLLECTION + ), + "data", + ), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_REALIZATIONS), "data" + ), + Input( + self.settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.COLORBY) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.PLOT_TYPE) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.FIG_LAYOUT_HEIGHT) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.COLORBY_GROUPING) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdCoverageView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsCoverage.Ids.SHOW_POINTS) + .to_string(), + "value", + ), + # prevent_initial_call=True, + ) + def _update_plots( + ensemble_names: List[str], + selector_dates: List[str], + selector_phases: List[str], + selector_well_names: List[str], + selector_well_collection_names: List[str], + selector_well_combine_type: str, + selector_realizations: List[int], + colorby: str, + plot_type: str, + figheight: int, + boxmode: str, + boxplot_points: str, + ) -> Union[str, List[Component]]: + + if not ensemble_names: + return "No ensembles selected" + + well_names = _get_well_names_combined( + self.well_collections, + selector_well_collection_names, + selector_well_names, + selector_well_combine_type, + ) + + if plot_type in ["diffplot", "rel_diffplot"]: + relative_diff = plot_type == "rel_diffplot" + dframe = makedf.get_df_diff( + makedf.get_df_smry( + self.input_provider_set, + ensemble_names, + self.ens_vectors, + self.ens_realizations, + selector_realizations, + well_names, + selector_phases, + selector_dates, + ), + relative_diff=relative_diff, + ) + figures = makefigs.coverage_diffplot( + dframe, + selector_phases, + colorby, + vector_type="well", + figheight=figheight, + boxmode=boxmode, + boxplot_points=boxplot_points, + ) + if plot_type == "crossplot": + dframe = makedf.get_df_smry( + self.input_provider_set, + ensemble_names, + self.ens_vectors, + self.ens_realizations, + selector_realizations, + well_names, + selector_phases, + selector_dates, + ) + figures = makefigs.coverage_crossplot( + dframe, + selector_phases, + colorby, + vector_type="well", + figheight=figheight, + boxplot_points=boxplot_points, + ) + + return figures diff --git a/webviz_subsurface/plugins/_prod_misfit/views/_well_production_heatmap.py b/webviz_subsurface/plugins/_prod_misfit/views/_well_production_heatmap.py new file mode 100644 index 000000000..56bc28208 --- /dev/null +++ b/webviz_subsurface/plugins/_prod_misfit/views/_well_production_heatmap.py @@ -0,0 +1,263 @@ +from typing import Dict, List, Union + +import webviz_core_components as wcc +from dash import Input, Output, callback +from dash.development.base_component import Component +from webviz_config.webviz_plugin_subclasses import SettingsGroupABC, ViewABC + +from ..._simulation_time_series._views._subplot_view._utils.provider_set import ( + ProviderSet, +) + +# from ..._simulation_time_series.types.provider_set import ProviderSet +from .._plugin_ids import PluginIds +from ..utils import make_dataframes as makedf +from ..utils import make_figures as makefigs +from ._view_functions import _get_well_names_combined + + +class PlotSettingsHeatmap(SettingsGroupABC): + class Ids: + # pylint: disable=too-few-public-methods + COLOR_RANGE_SCALING = "color-range-scaling" + FIG_LAYOUT_HEIGHT = "fig-layout-height" + PLOT_TYPE = "plot-type" + SHOW_WELLS_LARGEST_MISFIT = "show-wells-largest-misfit" + + def __init__(self) -> None: + super().__init__("Plot Settings") + + def layout(self) -> List[Component]: + return [ + wcc.Dropdown( + label="Show wells with largest misfit", + id=self.register_component_unique_id( + PlotSettingsHeatmap.Ids.SHOW_WELLS_LARGEST_MISFIT + ), + options=[ + {"label": "Show all", "value": 0}, + {"label": "2", "value": 2}, + {"label": "4", "value": 4}, + {"label": "6", "value": 6}, + {"label": "8", "value": 8}, + {"label": "10", "value": 10}, + {"label": "12", "value": 12}, + {"label": "15", "value": 15}, + {"label": "20", "value": 20}, + {"label": "25", "value": 25}, + ], + value=0, + multi=False, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Plot type", + id=self.register_component_unique_id(PlotSettingsHeatmap.Ids.PLOT_TYPE), + options=[ + {"label": "Mean misfit", "value": "diffplot"}, + { + "label": "Mean misfit relative (%)", + "value": "rel_diffplot", + }, + ], + value="diffplot", + multi=False, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Fig layout - height", + id=self.register_component_unique_id( + PlotSettingsHeatmap.Ids.FIG_LAYOUT_HEIGHT + ), + options=[ + { + "label": "Very small", + "value": 250, + }, + { + "label": "Small", + "value": 350, + }, + { + "label": "Medium", + "value": 450, + }, + { + "label": "Large", + "value": 700, + }, + { + "label": "Very large", + "value": 1000, + }, + ], + value=450, + clearable=False, + persistence=True, + persistence_type="memory", + ), + wcc.Dropdown( + label="Color range scaling (relative to max)", + id=self.register_component_unique_id( + PlotSettingsHeatmap.Ids.COLOR_RANGE_SCALING + ), + options=[ + {"label": f"{x:.0%}", "value": x} + for x in [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1.0, + 1.5, + 2.0, + ] + ], + style={"display": "block"}, + value=1.0, + clearable=False, + persistence=True, + persistence_type="memory", + ), + ] + + +class ProdHeatmapView(ViewABC): + class Ids: + # pylint: disable=too-few-public-methods + PLOT_SETTINGS = "plot-settings" + MAIN_COLUMN = "main-column" + + # pylint: disable=too-many-arguments + def __init__( + self, + input_provider_set: ProviderSet, + ens_vectors: Dict[str, List[str]], + ens_realizations: Dict[str, List[int]], + well_collections: Dict[str, List[str]], + ) -> None: + super().__init__("Well production heatmap") + + self.input_provider_set = input_provider_set + self.ens_vectors = ens_vectors + self.ens_realizations = ens_realizations + self.well_collections = well_collections + + self.add_settings_group( + PlotSettingsHeatmap(), ProdHeatmapView.Ids.PLOT_SETTINGS + ) + self.main_column = self.add_column(ProdHeatmapView.Ids.MAIN_COLUMN) + + def set_callbacks(self) -> None: + @callback( + Output( + self.layout_element(ProdHeatmapView.Ids.MAIN_COLUMN) + .get_unique_id() + .to_string(), + "children", + ), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_ENSEMBLES), "data" + ), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_DATES), "data"), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_PHASE), "data"), + Input(self.get_store_unique_id(PluginIds.Stores.SELECTED_WELLS), "data"), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_WELL_COLLECTIONS), + "data", + ), + Input( + self.get_store_unique_id( + PluginIds.Stores.SELECTED_COMBINE_WELLS_COLLECTION + ), + "data", + ), + Input( + self.get_store_unique_id(PluginIds.Stores.SELECTED_REALIZATIONS), "data" + ), + Input( + self.settings_group(ProdHeatmapView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsHeatmap.Ids.SHOW_WELLS_LARGEST_MISFIT) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdHeatmapView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsHeatmap.Ids.PLOT_TYPE) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdHeatmapView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsHeatmap.Ids.FIG_LAYOUT_HEIGHT) + .to_string(), + "value", + ), + Input( + self.settings_group(ProdHeatmapView.Ids.PLOT_SETTINGS) + .component_unique_id(PlotSettingsHeatmap.Ids.COLOR_RANGE_SCALING) + .to_string(), + "value", + ), + # prevent_initial_call=True, + ) + def _update_plots( + ensemble_names: List[str], + selector_dates: list, + selector_phases: list, + selector_well_names: list, + selector_well_collection_names: list, + selector_well_combine_type: str, + selector_realizations: list, + selector_filter_largest: int, + selector_plot_type: str, + selector_figheight: int, + selector_scale_col_range: float, + ) -> Union[str, List[Component]]: + # pylint disable=too-many-arguments + + if not ensemble_names: + return "No ensembles selected" + + well_names = _get_well_names_combined( + self.well_collections, + selector_well_collection_names, + selector_well_names, + selector_well_combine_type, + ) + + relative_diff = selector_plot_type == "rel_diffplot" + dframe = makedf.get_df_diff_stat( + makedf.get_df_diff( + makedf.get_df_smry( + self.input_provider_set, + ensemble_names, + self.ens_vectors, + self.ens_realizations, + selector_realizations, + well_names, + selector_phases, + selector_dates, + ), + relative_diff=relative_diff, + ) + ) + + figures = makefigs.heatmap_plot( + dframe, + selector_phases, + vector_type="well", + filter_largest=selector_filter_largest, + figheight=selector_figheight, + scale_col_range=selector_scale_col_range, + ) + return figures