From 9c88ba5df7741443a379417e1769141737995cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Thu, 20 Jan 2022 15:35:13 +0100 Subject: [PATCH 01/27] Prototyping backup --- webviz_subsurface/__init__.py | 41 ++++- .../_utils/user_defined_vector_description.py | 142 ++++++++++++++++++ .../_simulation_time_series/_callbacks.py | 6 +- .../_simulation_time_series/_plugin.py | 42 +++++- .../utils/provider_set_utils.py | 51 +++++-- 5 files changed, 265 insertions(+), 17 deletions(-) create mode 100644 webviz_subsurface/_utils/user_defined_vector_description.py diff --git a/webviz_subsurface/__init__.py b/webviz_subsurface/__init__.py index d8e663022..8735604d5 100644 --- a/webviz_subsurface/__init__.py +++ b/webviz_subsurface/__init__.py @@ -13,6 +13,10 @@ ConfigExpressionData, ) +from webviz_subsurface._utils.user_defined_vector_description import ( + USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA, +) + try: __version__ = get_distribution(__name__).version except DistributionNotFound: @@ -47,7 +51,7 @@ def subscribe_scratch_ensembles( @webviz_config.SHARED_SETTINGS_SUBSCRIPTIONS.subscribe("predefined_expressions") -def subcribe_predefined_expressions( +def subscribe_predefined_expressions( predefined_expressions: Optional[Dict[str, str]], config_folder: pathlib.Path, portable: bool, @@ -77,3 +81,38 @@ def subcribe_predefined_expressions( raise ValueError from err return output + + +@webviz_config.SHARED_SETTINGS_SUBSCRIPTIONS.subscribe( + "user_defined_vector_descriptions" +) +def subscribe_user_defined_vector_descriptions( + user_defined_vector_descriptions: Optional[Dict[str, str]], + config_folder: pathlib.Path, + portable: bool, +) -> Dict[str, pathlib.Path]: + + output: Dict[str, pathlib.Path] = {} + + if user_defined_vector_descriptions is None: + return output + + for key, path in user_defined_vector_descriptions.items(): + + if not pathlib.Path(path).is_absolute(): + output[key] = config_folder / path + + if not portable: + vector_descriptions: Dict[str, str] = yaml.safe_load( + output[key].read_text() + ) + + try: + jsonschema.validate( + instance=vector_descriptions, + schema=USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA, + ) + except jsonschema.exceptions.ValidationError as err: + raise ValueError from err + + return output diff --git a/webviz_subsurface/_utils/user_defined_vector_description.py b/webviz_subsurface/_utils/user_defined_vector_description.py new file mode 100644 index 000000000..9cd28c6f0 --- /dev/null +++ b/webviz_subsurface/_utils/user_defined_vector_description.py @@ -0,0 +1,142 @@ +import sys + +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, Optional + +import yaml + +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict + +# JSON Schema for user defined vector description +# Used as schema input for json_schema.validate() +USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA = { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "maxLength": 40, + }, + }, + "additionalProperties": False, + }, +} + +USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA_2 = { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "maxLength": 30, + }, + "type": { + "type": "string", + "maxLength": 15, + }, + }, + "additionalProperties": False, + }, +} + + +class ConfigRequiredUserDefinedVectorDescription(TypedDict): + """Definition of required user defined vector description data + + `Description:` + Data type to represent required user defined description for a vector + + `Required keys`: + * description: str - User defined description for vector + """ + + description: str + + +class ConfigUserDefinedVectorDescription( + ConfigRequiredUserDefinedVectorDescription, total=False +): + """Definition of user defined vector description data + + Contains both required and non-required keys. + + `Description:` + Data type to represent user defined description for a vector + + `Required keys`: + * description: str - User defined description for vector + + `Non-required keys`: + * type: str - Vector type used for VectorSelector category type + """ + + type: str + + +@dataclass(frozen=True) +class UserDefinedVectorDescriptionData: + description: str + type: Optional[str] + + +def create_user_defined_vector_descriptions_dict_from_config_2( + user_defined_vector_descriptions_path: Optional[Path], +) -> Dict[str, UserDefinedVectorDescriptionData]: + """Create user defined vector descriptions from config + + `Input:` + Path for yaml-file containing user defined vector descriptions + + `Return:` + Dict with vector as name, and user defined description dataclass as value. + """ + output: Dict[str, UserDefinedVectorDescriptionData] = {} + + if user_defined_vector_descriptions_path is None: + return output + + vector_descriptions_dict: Dict[ + str, ConfigUserDefinedVectorDescription + ] = yaml.safe_load(user_defined_vector_descriptions_path.read_text()) + + for vector, description_data in vector_descriptions_dict.items(): + _description = description_data.get("description", "") + _type = description_data.get("type", None) + output[vector] = UserDefinedVectorDescriptionData( + description=_description, type=_type + ) + + return output + + +def create_user_defined_vector_descriptions_dict_from_config( + user_defined_vector_descriptions_path: Optional[Path], +) -> Dict[str, str]: + """Create user defined vector descriptions from config + + `Input:` + Path for yaml-file containing user defined vector descriptions + + `Return:` + Dict with vector as name, and user defined description as value. + """ + output: Dict[str, str] = {} + if user_defined_vector_descriptions_path is None: + return output + + vector_description_dict: Dict[str, dict] = yaml.safe_load( + user_defined_vector_descriptions_path.read_text() + ) + + for vector, description in vector_description_dict.items(): + _description = description["description"] + output[vector] = _description + return output diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index 7cf9706b9..a92fc1450 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -61,6 +61,7 @@ def plugin_callbacks( initial_selected_vectors: List[str], vector_selector_base_data: list, observations: dict, # TODO: Improve typehint? + user_defined_vector_descriptions: Dict[str, str], line_shape_fallback: str = "linear", ) -> None: # TODO: Consider adding: presampled_frequency: Optional[Frequency] argument for use when @@ -220,7 +221,10 @@ def _update_graph( # Create unique colors based on all ensemble names to preserve consistent colors ensemble_colors = unique_colors(all_ensemble_names, theme) vector_titles = create_vector_plot_titles_from_provider_set( - vectors, selected_expressions, input_provider_set + vectors, + selected_expressions, + input_provider_set, + user_defined_vector_descriptions, ) figure_builder = VectorSubplotBuilder( vectors, diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 71c6d2aea..681cd1168 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -35,6 +35,10 @@ ) from .utils.from_timeseries_cumulatives import rename_vector_from_cumulative +from webviz_subsurface._utils.user_defined_vector_description import ( + create_user_defined_vector_descriptions_dict_from_config, +) + class SimulationTimeSeries(WebvizPluginABC): # pylint: disable=too-many-arguments,too-many-locals,too-many-statements @@ -49,6 +53,7 @@ def __init__( options: dict = None, sampling: str = Frequency.MONTHLY.value, predefined_expressions: str = None, + user_defined_vector_descriptions: str = None, line_shape_fallback: str = "linear", ) -> None: super().__init__() @@ -62,6 +67,23 @@ def __init__( self._webviz_settings = webviz_settings self._obsfile = obsfile + # Retreive user defined vector descriptions from configuration and validate + self._user_defined_vector_descriptions_path = ( + None + if user_defined_vector_descriptions is None + else webviz_settings.shared_settings["user_defined_vector_descriptions"][ + user_defined_vector_descriptions + ] + ) + # Vector name as key, description data as value + self._user_defined_vector_descriptions: Dict[ + str, str + ] = create_user_defined_vector_descriptions_dict_from_config( + get_path(self._user_defined_vector_descriptions_path) + if self._user_defined_vector_descriptions_path + else None + ) + self._line_shape_fallback = set_simulation_line_shape_fallback( line_shape_fallback ) @@ -124,15 +146,18 @@ def __init__( self._vector_calculator_data: list = [] for vector in non_historical_vector_names: split = vector.split(":") + description = self._user_defined_vector_descriptions.get( + split[0], simulation_vector_description(split[0]) + ) add_vector_to_vector_selector_data( self._vector_selector_base_data, vector, - simulation_vector_description(split[0]), + description, ) add_vector_to_vector_selector_data( self._vector_calculator_data, vector, - simulation_vector_description(split[0]), + description, ) metadata = ( @@ -151,15 +176,19 @@ def __init__( avgrate_split = avgrate_vec.split(":") interval_split = interval_vec.split(":") + user_defined_description = self._user_defined_vector_descriptions.get( + split[0], None + ) + add_vector_to_vector_selector_data( self._vector_selector_base_data, avgrate_vec, - f"{simulation_vector_description(avgrate_split[0])} ({avgrate_vec})", + f"{simulation_vector_description(avgrate_split[0], user_defined_description)}", ) add_vector_to_vector_selector_data( self._vector_selector_base_data, interval_vec, - f"{simulation_vector_description(interval_split[0])} ({interval_vec})", + f"{simulation_vector_description(interval_split[0], user_defined_description)}", ) # Retreive predefined expressions from configuration and validate @@ -235,6 +264,7 @@ def set_callbacks(self, app: dash.Dash) -> None: initial_selected_vectors=self._initial_vectors, vector_selector_base_data=self._vector_selector_base_data, observations=self._observations, + user_defined_vector_descriptions=self._user_defined_vector_descriptions, line_shape_fallback=self._line_shape_fallback, ) @@ -348,4 +378,8 @@ def add_webvizstore(self) -> List[Tuple[Callable, list]]: functions.append((get_path, [{"path": self._obsfile}])) if self._predefined_expressions_path: functions.append((get_path, [{"path": self._predefined_expressions_path}])) + if self._user_defined_vector_descriptions_path: + functions.append( + (get_path, [{"path": self._user_defined_vector_descriptions_path}]) + ) return functions diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py index 279299ddc..79f9eb3e8 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py @@ -9,6 +9,10 @@ VectorCalculator, get_expression_from_name, ) +from webviz_subsurface.plugins._simulation_time_series.utils.from_timeseries_cumulatives import ( + get_cumulative_vector_name, + is_interval_or_average_vector, +) from ..types import ProviderSet @@ -17,6 +21,7 @@ def create_vector_plot_titles_from_provider_set( vector_names: List[str], expressions: List[ExpressionInfo], provider_set: ProviderSet, + user_defined_vector_descriptions: Dict[str, str], ) -> Dict[str, str]: """Create plot titles for vectors @@ -30,22 +35,46 @@ def create_vector_plot_titles_from_provider_set( all_vector_names = provider_set.all_vector_names() for vector_name in vector_names: - vector = vector_name - - if vector.startswith("AVG_"): - vector = vector.lstrip("AVG_") - if vector.startswith("INTVL_"): - vector = vector.lstrip("INTVL_") - if vector in all_vector_names: - metadata = provider_set.vector_metadata(vector) - title = simulation_vector_description(vector_name) + # Provider vector + if vector_name in all_vector_names: + metadata = provider_set.vector_metadata(vector_name) + title = user_defined_vector_descriptions.get( + vector_name.split(":")[0], simulation_vector_description(vector_name) + ) if metadata and metadata.unit: + title += f" [{simulation_unit_reformat(metadata.unit)}]" + vector_title_dict[vector_name] = title + + # INTVL_ or AVG_ vector + elif is_interval_or_average_vector(vector_name): + vector = vector_name + cumulative_vector = get_cumulative_vector_name(vector_name) + title = "" + if vector.startswith("AVG_"): + vector = vector.lstrip("AVG_") + title = user_defined_vector_descriptions.get( + cumulative_vector.split(":")[0], "" + ) title = ( - f"{simulation_vector_description(vector_name)}" - f" [{simulation_unit_reformat(metadata.unit)}]" + title + "Per Day" + if title + else simulation_vector_description(vector) ) + + if vector.startswith("INTVL_"): + vector = vector.lstrip("INTVL_") + title = user_defined_vector_descriptions.get( + cumulative_vector.split(":")[0], + simulation_vector_description(vector), + ) + + metadata = provider_set.vector_metadata(vector) + if metadata and metadata.unit: + title += f" [{simulation_unit_reformat(metadata.unit)}]" vector_title_dict[vector_name] = title + + # Calculated vector else: expression = get_expression_from_name(vector_name, expressions) if expression: From cf41b23dc7cfad853f22a6fd4d5d7bc531c23482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Wed, 26 Jan 2022 15:33:46 +0100 Subject: [PATCH 02/27] Furhter implementation - Use naming "VectorDefinition" - Adjust to "AVG_" -> "PER_DAY_" and "INTVL_" -> "PER_INTVL_" --- webviz_subsurface/__init__.py | 22 ++- .../_abbreviations/reservoir_simulation.py | 14 +- .../_utils/user_defined_vector_definitions.py | 111 ++++++++++++++ .../_utils/user_defined_vector_description.py | 142 ------------------ webviz_subsurface/_utils/vector_selector.py | 43 ++++-- .../_simulation_time_series/_callbacks.py | 24 ++- .../_simulation_time_series/_layout.py | 10 +- .../_simulation_time_series/_plugin.py | 111 +++++++++----- ...ed_delta_ensemble_vectors_accessor_impl.py | 52 +++---- .../derived_ensemble_vectors_accessor_impl.py | 52 +++---- .../types/derived_vectors_accessor.py | 4 +- .../utils/create_vector_traces_utils.py | 4 +- .../utils/from_timeseries_cumulatives.py | 71 +++++---- .../utils/provider_set_utils.py | 46 +++--- .../utils/trace_line_shape.py | 4 +- 15 files changed, 383 insertions(+), 327 deletions(-) create mode 100644 webviz_subsurface/_utils/user_defined_vector_definitions.py delete mode 100644 webviz_subsurface/_utils/user_defined_vector_description.py diff --git a/webviz_subsurface/__init__.py b/webviz_subsurface/__init__.py index 8735604d5..0a4dd6d8d 100644 --- a/webviz_subsurface/__init__.py +++ b/webviz_subsurface/__init__.py @@ -13,8 +13,8 @@ ConfigExpressionData, ) -from webviz_subsurface._utils.user_defined_vector_description import ( - USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA, +from webviz_subsurface._utils.user_defined_vector_definitions import ( + USER_DEFINED_VECTOR_DEFINITIONS_JSON_SCHEMA, ) try: @@ -84,33 +84,31 @@ def subscribe_predefined_expressions( @webviz_config.SHARED_SETTINGS_SUBSCRIPTIONS.subscribe( - "user_defined_vector_descriptions" + "user_defined_vector_definitions" ) -def subscribe_user_defined_vector_descriptions( - user_defined_vector_descriptions: Optional[Dict[str, str]], +def subscribe_user_defined_vector_definitions( + user_defined_vector_definitions: Optional[Dict[str, str]], config_folder: pathlib.Path, portable: bool, ) -> Dict[str, pathlib.Path]: output: Dict[str, pathlib.Path] = {} - if user_defined_vector_descriptions is None: + if user_defined_vector_definitions is None: return output - for key, path in user_defined_vector_descriptions.items(): + for key, path in user_defined_vector_definitions.items(): if not pathlib.Path(path).is_absolute(): output[key] = config_folder / path if not portable: - vector_descriptions: Dict[str, str] = yaml.safe_load( - output[key].read_text() - ) + vector_definitions: Dict[str, str] = yaml.safe_load(output[key].read_text()) try: jsonschema.validate( - instance=vector_descriptions, - schema=USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA, + instance=vector_definitions, + schema=USER_DEFINED_VECTOR_DEFINITIONS_JSON_SCHEMA, ) except jsonschema.exceptions.ValidationError as err: raise ValueError from err diff --git a/webviz_subsurface/_abbreviations/reservoir_simulation.py b/webviz_subsurface/_abbreviations/reservoir_simulation.py index 10fa2e284..ff0d4e07f 100644 --- a/webviz_subsurface/_abbreviations/reservoir_simulation.py +++ b/webviz_subsurface/_abbreviations/reservoir_simulation.py @@ -5,11 +5,11 @@ import pandas as pd +from webviz_subsurface_components import VectorDefinitions + _DATA_PATH = pathlib.Path(__file__).parent.absolute() / "abbreviation_data" -SIMULATION_VECTOR_TERMINOLOGY = json.loads( - (_DATA_PATH / "reservoir_simulation_vectors.json").read_text() -) +SIMULATION_VECTOR_TERMINOLOGY = VectorDefinitions RESERVOIR_SIMULATION_UNIT_TERMINOLOGY = json.loads( (_DATA_PATH / "reservoir_simulation_unit_terminology.json").read_text() @@ -39,13 +39,21 @@ def simulation_vector_base(vector: str) -> str: def simulation_vector_description(vector: str) -> str: """Returns a more human friendly description of the simulation vector if possible, otherwise returns the input as is. + + # TODO: Remove support for "AVG_" and "INTVL_" when all usage is deprecated """ if vector.startswith("AVG_"): prefix = "Average " vector = vector[4:] + elif vector.startswith("PER_DAY_"): + prefix = "Average " + vector = vector[8:] elif vector.startswith("INTVL_"): prefix = "Interval " vector = vector[6:] + elif vector.startswith("PER_INTVL_"): + prefix = "Interval " + vector = vector[10:] else: prefix = "" diff --git a/webviz_subsurface/_utils/user_defined_vector_definitions.py b/webviz_subsurface/_utils/user_defined_vector_definitions.py new file mode 100644 index 000000000..705efe5db --- /dev/null +++ b/webviz_subsurface/_utils/user_defined_vector_definitions.py @@ -0,0 +1,111 @@ +import sys + +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, Optional + +import yaml + +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict + +# JSON Schema for user defined vector definitions +# Used as schema input for json_schema.validate() +USER_DEFINED_VECTOR_DEFINITIONS_JSON_SCHEMA = { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "maxLength": 50, + }, + "type": { + "type": "string", + "maxLength": 15, + }, + }, + "additionalProperties": False, + }, +} + + +class ConfigRequiredUserDefinedVectorDefinition(TypedDict): + """Definition of required user defined vector definition data + + `Description:` + Data type to represent required user defined data for a vector definition + + `Required keys`: + * description: str - User defined description for vector + """ + + description: str + + +class ConfigUserDefinedVectorDefinition( + ConfigRequiredUserDefinedVectorDefinition, total=False +): + """Definition of user defined vector definition data + + Contains both required and non-required keys. + + `Description:` + Data type to represent user defined data for a vector definition + + `Required keys`: + * description: str - User defined description for vector + + `Non-required keys`: + * type: str - Vector type used for VectorSelector category type + """ + + type: str + + +@dataclass(frozen=True) +class UserDefinedVectorDefinition: + description: str + type: Optional[str] + + +def create_user_defined_vector_descriptions_from_config( + user_defined_vector_data_path: Optional[Path], +) -> Dict[str, UserDefinedVectorDefinition]: + """Create user defined vector data from config + + `Input:` + Path for yaml-file containing user defined vector data + + `Return:` + Dict with vector as name, and user defined vector data object as value. + """ + output: Dict[str, UserDefinedVectorDefinition] = {} + + if user_defined_vector_data_path is None: + return output + + vector_data_dict: Dict[str, ConfigUserDefinedVectorDefinition] = yaml.safe_load( + user_defined_vector_data_path.read_text() + ) + + for vector, vector_data in vector_data_dict.items(): + _description = vector_data.get("description", "") + _type = vector_data.get("type", None) + output[vector] = UserDefinedVectorDefinition( + description=_description, type=_type + ) + + return output + + +def create_user_defined_vector_description_dict( + user_defined_vector_definitions: Dict[str, UserDefinedVectorDefinition] +) -> Dict[str, str]: + output: Dict[str, str] = {} + for elm in user_defined_vector_definitions: + output[elm] = user_defined_vector_definitions[elm].description + return output diff --git a/webviz_subsurface/_utils/user_defined_vector_description.py b/webviz_subsurface/_utils/user_defined_vector_description.py deleted file mode 100644 index 9cd28c6f0..000000000 --- a/webviz_subsurface/_utils/user_defined_vector_description.py +++ /dev/null @@ -1,142 +0,0 @@ -import sys - -from dataclasses import dataclass -from pathlib import Path -from typing import Dict, Optional - -import yaml - -if sys.version_info >= (3, 8): - from typing import TypedDict -else: - from typing_extensions import TypedDict - -# JSON Schema for user defined vector description -# Used as schema input for json_schema.validate() -USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA = { - "type": "object", - "additionalProperties": { - "type": "object", - "required": ["description"], - "properties": { - "description": { - "type": "string", - "maxLength": 40, - }, - }, - "additionalProperties": False, - }, -} - -USER_DEFINED_VECTOR_DESCRIPTION_JSON_SCHEMA_2 = { - "type": "object", - "additionalProperties": { - "type": "object", - "required": ["description"], - "properties": { - "description": { - "type": "string", - "maxLength": 30, - }, - "type": { - "type": "string", - "maxLength": 15, - }, - }, - "additionalProperties": False, - }, -} - - -class ConfigRequiredUserDefinedVectorDescription(TypedDict): - """Definition of required user defined vector description data - - `Description:` - Data type to represent required user defined description for a vector - - `Required keys`: - * description: str - User defined description for vector - """ - - description: str - - -class ConfigUserDefinedVectorDescription( - ConfigRequiredUserDefinedVectorDescription, total=False -): - """Definition of user defined vector description data - - Contains both required and non-required keys. - - `Description:` - Data type to represent user defined description for a vector - - `Required keys`: - * description: str - User defined description for vector - - `Non-required keys`: - * type: str - Vector type used for VectorSelector category type - """ - - type: str - - -@dataclass(frozen=True) -class UserDefinedVectorDescriptionData: - description: str - type: Optional[str] - - -def create_user_defined_vector_descriptions_dict_from_config_2( - user_defined_vector_descriptions_path: Optional[Path], -) -> Dict[str, UserDefinedVectorDescriptionData]: - """Create user defined vector descriptions from config - - `Input:` - Path for yaml-file containing user defined vector descriptions - - `Return:` - Dict with vector as name, and user defined description dataclass as value. - """ - output: Dict[str, UserDefinedVectorDescriptionData] = {} - - if user_defined_vector_descriptions_path is None: - return output - - vector_descriptions_dict: Dict[ - str, ConfigUserDefinedVectorDescription - ] = yaml.safe_load(user_defined_vector_descriptions_path.read_text()) - - for vector, description_data in vector_descriptions_dict.items(): - _description = description_data.get("description", "") - _type = description_data.get("type", None) - output[vector] = UserDefinedVectorDescriptionData( - description=_description, type=_type - ) - - return output - - -def create_user_defined_vector_descriptions_dict_from_config( - user_defined_vector_descriptions_path: Optional[Path], -) -> Dict[str, str]: - """Create user defined vector descriptions from config - - `Input:` - Path for yaml-file containing user defined vector descriptions - - `Return:` - Dict with vector as name, and user defined description as value. - """ - output: Dict[str, str] = {} - if user_defined_vector_descriptions_path is None: - return output - - vector_description_dict: Dict[str, dict] = yaml.safe_load( - user_defined_vector_descriptions_path.read_text() - ) - - for vector, description in vector_description_dict.items(): - _description = description["description"] - output[vector] = _description - return output diff --git a/webviz_subsurface/_utils/vector_selector.py b/webviz_subsurface/_utils/vector_selector.py index b04ef026a..aed916575 100644 --- a/webviz_subsurface/_utils/vector_selector.py +++ b/webviz_subsurface/_utils/vector_selector.py @@ -1,7 +1,14 @@ +from typing import Dict, Optional + +from webviz_subsurface._utils.user_defined_vector_definitions import ( + UserDefinedVectorDefinition, +) + + def add_vector_to_vector_selector_data( vector_selector_data: list, vector: str, - description: str, + description: Optional[str] = None, description_at_last_node: bool = False, ) -> None: nodes = vector.split(":") @@ -15,16 +22,17 @@ def add_vector_to_vector_selector_data( found = True break if not found: - description_text = description if index == 0 else "" - if description_at_last_node: - description_text = description if index == len(nodes) - 1 else "" - current_child_list.append( - { - "name": node, - "description": description_text, - "children": [] if index < len(nodes) - 1 else None, - } - ) + node_data: dict = { + "name": node, + "children": [] if index < len(nodes) - 1 else None, + } + if not description_at_last_node and description and index == 0: + node_data["description"] = description + if description_at_last_node and description and (index == len(nodes) - 1): + node_data["description"] = description + + current_child_list.append(node_data) + children = current_child_list[-1]["children"] current_child_list = children if children is not None else [] @@ -45,3 +53,16 @@ def is_vector_name_in_vector_selector_data( if not found: return False return found + + +def create_custom_vector_definition_from_user_defined_vector_data( + user_defined_vector_data: Dict[str, UserDefinedVectorDefinition] +) -> dict: + output: dict = {} + for elm in user_defined_vector_data: + _type = user_defined_vector_data[elm].type + output[elm] = { + "description": user_defined_vector_data[elm].description, + "type": _type if _type is not None else "others", + } + return output diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index a92fc1450..5b53ddf8e 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -43,7 +43,10 @@ from .utils.derived_ensemble_vectors_accessor_utils import ( create_derived_vectors_accessor_dict, ) -from .utils.from_timeseries_cumulatives import datetime_to_intervalstr +from .utils.from_timeseries_cumulatives import ( + datetime_to_intervalstr, + is_per_interval_or_per_day_vector, +) from .utils.history_vectors import create_history_vectors_df from .utils.provider_set_utils import create_vector_plot_titles_from_provider_set from .utils.trace_line_shape import get_simulation_line_shape @@ -60,6 +63,7 @@ def plugin_callbacks( theme: WebvizConfigTheme, initial_selected_vectors: List[str], vector_selector_base_data: list, + custom_vector_definitions_base: dict, observations: dict, # TODO: Improve typehint? user_defined_vector_descriptions: Dict[str, str], line_shape_fallback: str = "linear", @@ -153,7 +157,7 @@ def _update_graph( NOTE: __graph_data_has_changed_trigger is only used to trigger callback when change of graphs data has changed and re-render of graph is necessary. E.g. when a selected expression - from the VectorCalculatorgets edited, without changing the expression name - i.e. + from the VectorCalculator gets edited without changing the expression name - i.e. VectorSelector selectedNodes remain unchanged. """ if not isinstance(selected_ensembles, list): @@ -224,6 +228,7 @@ def _update_graph( vectors, selected_expressions, input_provider_set, + resampling_frequency, user_defined_vector_descriptions, ) figure_builder = VectorSubplotBuilder( @@ -285,9 +290,9 @@ def _update_graph( vectors_df_list.append( accessor.get_provider_vectors_df(realizations=realizations_query) ) - if accessor.has_interval_and_average_vectors(): + if accessor.has_per_interval_and_per_day_vectors(): vectors_df_list.append( - accessor.create_interval_and_average_vectors_df( + accessor.create_per_interval_and_per_day_vectors_df( realizations=realizations_query ) ) @@ -537,9 +542,9 @@ def _user_download_data( vectors_df_list.append( accessor.get_provider_vectors_df(realizations=realizations_query) ) - if accessor.has_interval_and_average_vectors(): + if accessor.has_per_interval_and_per_day_vectors(): vectors_df_list.append( - accessor.create_interval_and_average_vectors_df( + accessor.create_per_interval_and_per_day_vectors_df( realizations=realizations_query ) ) @@ -575,7 +580,7 @@ def _user_download_data( loc=0, column="ENSEMBLE", value=ensemble_name_list ) - if vector.startswith(("AVG_", "INTVL_")): + if is_per_interval_or_per_day_vector(vector): vector_df["DATE"] = vector_df["DATE"].apply( datetime_to_intervalstr, freq=resampling_frequency ) @@ -607,7 +612,7 @@ def _user_download_data( vector_key = vector + "_statistics" - if vector.startswith(("AVG_", "INTVL_")): + if is_per_interval_or_per_day_vector(vector): vector_statistics_df.loc[ :, ("DATE", "") ] = vector_statistics_df.loc[:, ("DATE", "")].apply( @@ -844,6 +849,9 @@ def _update_vector_calculator_expressions_on_modal_close( new_custom_vector_definitions = get_custom_vector_definitions_from_expressions( new_expressions ) + for key, value in custom_vector_definitions_base.items(): + if key not in new_custom_vector_definitions: + new_custom_vector_definitions[key] = value # Prevent updates if unchanged if new_custom_vector_definitions == current_custom_vector_definitions: diff --git a/webviz_subsurface/plugins/_simulation_time_series/_layout.py b/webviz_subsurface/plugins/_simulation_time_series/_layout.py index 62c88b5db..2cf823454 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_layout.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_layout.py @@ -7,9 +7,6 @@ from webviz_subsurface_components import ExpressionInfo from webviz_subsurface._providers import Frequency -from webviz_subsurface._utils.vector_calculator import ( - get_custom_vector_definitions_from_expressions, -) from .types import ( FanchartOptions, @@ -84,6 +81,7 @@ def main_layout( vector_selector_data: list, vector_calculator_data: list, predefined_expressions: List[ExpressionInfo], + custom_vector_definitions: dict, realizations: List[int], disable_resampling_dropdown: bool, selected_resampling_frequency: Frequency, @@ -104,6 +102,7 @@ def main_layout( vector_selector_data=vector_selector_data, vector_calculator_data=vector_calculator_data, predefined_expressions=predefined_expressions, + custom_vector_definitions=custom_vector_definitions, realizations=realizations, disable_resampling_dropdown=disable_resampling_dropdown, selected_resampling_frequency=selected_resampling_frequency, @@ -148,6 +147,7 @@ def __settings_layout( vector_selector_data: list, vector_calculator_data: list, predefined_expressions: List[ExpressionInfo], + custom_vector_definitions: dict, realizations: List[int], disable_resampling_dropdown: bool, selected_resampling_frequency: Frequency, @@ -241,9 +241,7 @@ def __settings_layout( else selected_vectors, numSecondsUntilSuggestionsAreShown=0.5, lineBreakAfterTag=True, - customVectorDefinitions=get_custom_vector_definitions_from_expressions( - predefined_expressions - ), + customVectorDefinitions=custom_vector_definitions, ), html.Button( "Vector Calculator", diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 681cd1168..080e1e1f7 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -8,10 +8,11 @@ from webviz_config import WebvizPluginABC, WebvizSettings from webviz_config.webviz_assets import WEBVIZ_ASSETS +import webviz_subsurface_components as wsc + import webviz_subsurface from webviz_subsurface._abbreviations.reservoir_simulation import ( historical_vector, - simulation_vector_description, ) from webviz_subsurface._providers import Frequency from webviz_subsurface._utils.simulation_timeseries import ( @@ -33,15 +34,27 @@ create_lazy_provider_set_from_paths, create_presampled_provider_set_from_paths, ) -from .utils.from_timeseries_cumulatives import rename_vector_from_cumulative - -from webviz_subsurface._utils.user_defined_vector_description import ( - create_user_defined_vector_descriptions_dict_from_config, +from .utils.from_timeseries_cumulatives import ( + create_per_interval_or_per_day_vector_description, + create_per_day_vector_name, + create_per_interval_vector_name, +) +from webviz_subsurface._utils.vector_calculator import ( + get_custom_vector_definitions_from_expressions, +) +from webviz_subsurface._utils.vector_selector import ( + create_custom_vector_definition_from_user_defined_vector_data, ) +from webviz_subsurface._utils.user_defined_vector_definitions import ( + create_user_defined_vector_descriptions_from_config, + create_user_defined_vector_description_dict, + UserDefinedVectorDefinition, +) +# pylint: disable=too-many-instance-attributes class SimulationTimeSeries(WebvizPluginABC): - # pylint: disable=too-many-arguments,too-many-locals,too-many-statements + # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements def __init__( self, app: dash.Dash, @@ -53,7 +66,7 @@ def __init__( options: dict = None, sampling: str = Frequency.MONTHLY.value, predefined_expressions: str = None, - user_defined_vector_descriptions: str = None, + user_defined_vector_definitions: str = None, line_shape_fallback: str = "linear", ) -> None: super().__init__() @@ -70,19 +83,29 @@ def __init__( # Retreive user defined vector descriptions from configuration and validate self._user_defined_vector_descriptions_path = ( None - if user_defined_vector_descriptions is None - else webviz_settings.shared_settings["user_defined_vector_descriptions"][ - user_defined_vector_descriptions + if user_defined_vector_definitions is None + else webviz_settings.shared_settings["user_defined_vector_definitions"][ + user_defined_vector_definitions ] ) # Vector name as key, description data as value - self._user_defined_vector_descriptions: Dict[ - str, str - ] = create_user_defined_vector_descriptions_dict_from_config( + _user_defined_vector_definitions: Dict[ + str, UserDefinedVectorDefinition + ] = create_user_defined_vector_descriptions_from_config( get_path(self._user_defined_vector_descriptions_path) if self._user_defined_vector_descriptions_path else None ) + self._user_defined_vector_descriptions = ( + create_user_defined_vector_description_dict( + _user_defined_vector_definitions + ) + ) + self._custom_vector_definitions = ( + create_custom_vector_definition_from_user_defined_vector_data( + _user_defined_vector_definitions + ) + ) self._line_shape_fallback = set_simulation_line_shape_fallback( line_shape_fallback @@ -145,19 +168,15 @@ def __init__( self._vector_selector_base_data: list = [] self._vector_calculator_data: list = [] for vector in non_historical_vector_names: - split = vector.split(":") - description = self._user_defined_vector_descriptions.get( - split[0], simulation_vector_description(split[0]) - ) add_vector_to_vector_selector_data( self._vector_selector_base_data, vector, - description, ) + + # Only vectors from providers are provided to vector calculator add_vector_to_vector_selector_data( self._vector_calculator_data, vector, - description, ) metadata = ( @@ -168,29 +187,40 @@ def __init__( if metadata and metadata.is_total: # Get the likely name for equivalent rate vector and make dropdown options. # Requires that the time_index was either defined or possible to infer. - avgrate_vec = rename_vector_from_cumulative(vector=vector, as_rate=True) - interval_vec = rename_vector_from_cumulative( - vector=vector, as_rate=False - ) - - avgrate_split = avgrate_vec.split(":") - interval_split = interval_vec.split(":") - - user_defined_description = self._user_defined_vector_descriptions.get( - split[0], None - ) + per_day_vec = create_per_day_vector_name(vector) + per_intvl_vec = create_per_interval_vector_name(vector) add_vector_to_vector_selector_data( self._vector_selector_base_data, - avgrate_vec, - f"{simulation_vector_description(avgrate_split[0], user_defined_description)}", + per_day_vec, ) add_vector_to_vector_selector_data( self._vector_selector_base_data, - interval_vec, - f"{simulation_vector_description(interval_split[0], user_defined_description)}", + per_intvl_vec, ) + # Add vector base to custom vector definition if not existing + # TODO: Make an util? + vector_base = vector.split(":")[0] + _definition = wsc.VectorDefinitions.get(vector_base, None) + _type = _definition["type"] if _definition else "others" + per_day_vec_base = per_day_vec.split(":")[0] + per_intvl_vec_base = per_intvl_vec.split(":")[0] + if per_day_vec_base not in self._custom_vector_definitions: + self._custom_vector_definitions[per_day_vec_base] = { + "type": _type, + "description": create_per_interval_or_per_day_vector_description( + per_day_vec, self._user_defined_vector_descriptions + ), + } + if per_intvl_vec_base not in self._custom_vector_definitions: + self._custom_vector_definitions[per_intvl_vec_base] = { + "type": _type, + "description": create_per_interval_or_per_day_vector_description( + per_intvl_vec_base, self._user_defined_vector_descriptions + ), + } + # Retreive predefined expressions from configuration and validate self._predefined_expressions_path = ( None @@ -212,6 +242,17 @@ def __init__( warnings.warn(message) expression["isValid"] = valid + # Add expressions to custom vector definitions + _custom_vector_definitions_from_expressions = ( + get_custom_vector_definitions_from_expressions(self._predefined_expressions) + ) + self._custom_vector_definitions_base = copy.deepcopy( + self._custom_vector_definitions + ) + for key, value in _custom_vector_definitions_from_expressions.items(): + if key not in self._custom_vector_definitions: + self._custom_vector_definitions[key] = value + # Create initial vector selector data with predefined expressions self._initial_vector_selector_data = copy.deepcopy( self._vector_selector_base_data @@ -246,6 +287,7 @@ def layout(self) -> wcc.FlexBox: vector_selector_data=self._initial_vector_selector_data, vector_calculator_data=self._vector_calculator_data, predefined_expressions=self._predefined_expressions, + custom_vector_definitions=self._custom_vector_definitions, realizations=self._input_provider_set.all_realizations(), disable_resampling_dropdown=self._presampled_frequency is not None, selected_resampling_frequency=self._sampling, @@ -263,6 +305,7 @@ def set_callbacks(self, app: dash.Dash) -> None: theme=self._theme, initial_selected_vectors=self._initial_vectors, vector_selector_base_data=self._vector_selector_base_data, + custom_vector_definitions_base=self._custom_vector_definitions_base, observations=self._observations, user_defined_vector_descriptions=self._user_defined_vector_descriptions, line_shape_fallback=self._line_shape_fallback, diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py index 7c11ba853..0d7947822 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py @@ -12,7 +12,7 @@ from ..utils.from_timeseries_cumulatives import ( calculate_from_resampled_cumulative_vectors_df, get_cumulative_vector_name, - is_interval_or_average_vector, + is_per_interval_or_per_day_vector, ) from .derived_vectors_accessor import DerivedVectorsAccessor @@ -26,8 +26,8 @@ class DerivedDeltaEnsembleVectorsAccessorImpl(DerivedVectorsAccessor): A sequence of vector names are provided, and data is fetched or created based on which type of vectors are present in the sequence. - Vector names can be regular vectors existing among vector names in the providers, Interval - Delta/Average rate vector or a calculated vector from vector calculator. + Vector names can be regular vectors existing among vector names in the providers, Per + Interval/Per Day vector or a calculated vector from vector calculator. Based on the vector type, the class provides an interface for retrieveing dataframes for the set of such vectors for the provider. @@ -79,10 +79,10 @@ def __init__( self._provider_vectors = [ vector for vector in vectors if vector in _accessor_vectors ] - self._interval_and_average_vectors = [ + self._per_interval_and_per_day_vectors = [ vector for vector in vectors - if is_interval_or_average_vector(vector) + if is_per_interval_or_per_day_vector(vector) and get_cumulative_vector_name(vector) in _accessor_vectors ] self._vector_calculator_expressions = ( @@ -150,8 +150,8 @@ def __create_delta_ensemble_vectors_df( def has_provider_vectors(self) -> bool: return len(self._provider_vectors) > 0 - def has_interval_and_average_vectors(self) -> bool: - return len(self._interval_and_average_vectors) > 0 + def has_per_interval_and_per_day_vectors(self) -> bool: + return len(self._per_interval_and_per_day_vectors) > 0 def has_vector_calculator_expressions(self) -> bool: return len(self._vector_calculator_expressions) > 0 @@ -178,16 +178,16 @@ def get_provider_vectors_df( self._provider_vectors, self._resampling_frequency, realizations ) - def create_interval_and_average_vectors_df( + def create_per_interval_and_per_day_vectors_df( self, realizations: Optional[Sequence[int]] = None, ) -> pd.DataFrame: - """Get dataframe with interval delta and average rate vector data for provided vectors. + """Get dataframe with interval delta and per day delta vector data for provided vectors. The returned dataframe contains columns with name of vector and corresponding interval delta - or average rate data. + or per day delta data. - Interval delta and average rate date is calculated with same sampling frequency as provider + Interval delta and per day delta date is calculated with same sampling frequency as provider is set with. I.e. resampling frequency is given for providers supporting resampling, otherwise sampling frequency is fixed. @@ -203,16 +203,16 @@ def create_interval_and_average_vectors_df( * Handle calculation of cumulative when raw data is added * See TODO in calculate_from_resampled_cumulative_vectors_df() """ - if not self.has_interval_and_average_vectors(): + if not self.has_per_interval_and_per_day_vectors(): raise ValueError( - f'Vector data handler for provider "{self._name}" has no interval delta ' - "and average rate vector names" + f'Vector data handler for provider "{self._name}" has no per interval ' + "or per day vector names" ) cumulative_vector_names = [ get_cumulative_vector_name(elm) - for elm in self._interval_and_average_vectors - if is_interval_or_average_vector(elm) + for elm in self._per_interval_and_per_day_vectors + if is_per_interval_or_per_day_vector(elm) ] cumulative_vector_names = list(sorted(set(cumulative_vector_names))) @@ -220,25 +220,25 @@ def create_interval_and_average_vectors_df( cumulative_vector_names, self._resampling_frequency, realizations ) - interval_and_average_vectors_df = pd.DataFrame() - for vector_name in self._interval_and_average_vectors: + per_interval_and_per_day_vectors_df = pd.DataFrame() + for vector_name in self._per_interval_and_per_day_vectors: cumulative_vector_name = get_cumulative_vector_name(vector_name) - interval_and_average_vector_df = ( + per_interval_or_per_day_vector_df = ( calculate_from_resampled_cumulative_vectors_df( vectors_df[["DATE", "REAL", cumulative_vector_name]], - as_rate_per_day=vector_name.startswith("AVG_"), + as_rate_per_day=vector_name.startswith("PER_DAY_"), ) ) - if interval_and_average_vectors_df.empty: - interval_and_average_vectors_df = interval_and_average_vector_df + if per_interval_and_per_day_vectors_df.empty: + per_interval_and_per_day_vectors_df = per_interval_or_per_day_vector_df else: - interval_and_average_vectors_df = pd.merge( - interval_and_average_vectors_df, - interval_and_average_vector_df, + per_interval_and_per_day_vectors_df = pd.merge( + per_interval_and_per_day_vectors_df, + per_interval_or_per_day_vector_df, how="inner", ) - return interval_and_average_vectors_df + return per_interval_and_per_day_vectors_df def create_calculated_vectors_df( self, realizations: Optional[Sequence[int]] = None diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py index 0da6991a8..eab726701 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py @@ -12,7 +12,7 @@ from ..utils.from_timeseries_cumulatives import ( calculate_from_resampled_cumulative_vectors_df, get_cumulative_vector_name, - is_interval_or_average_vector, + is_per_interval_or_per_day_vector, ) from .derived_vectors_accessor import DerivedVectorsAccessor @@ -26,8 +26,8 @@ class DerivedEnsembleVectorsAccessorImpl(DerivedVectorsAccessor): A sequence of vector names are provided, and data is fetched or created based on which type of vectors are present in the sequence. - Vector names can be regular vectors existing among vector names in the provider, Interval - Delta/Average rate vector or a calculated vector from vector calculator. + Vector names can be regular vectors existing among vector names in the provider, Per + Interval/Per Day vector or a calculated vector from vector calculator. Based on the vector type, the class provides an interface for retrieveing dataframes for the set of such vectors for the provider. @@ -49,10 +49,10 @@ def __init__( self._provider_vectors = [ vector for vector in vectors if vector in self._provider.vector_names() ] - self._interval_and_average_vectors = [ + self._per_interval_and_per_day_vectors = [ vector for vector in vectors - if is_interval_or_average_vector(vector) + if is_per_interval_or_per_day_vector(vector) and get_cumulative_vector_name(vector) in provider.vector_names() ] self._vector_calculator_expressions = ( @@ -67,8 +67,8 @@ def __init__( def has_provider_vectors(self) -> bool: return len(self._provider_vectors) > 0 - def has_interval_and_average_vectors(self) -> bool: - return len(self._interval_and_average_vectors) > 0 + def has_per_interval_and_per_day_vectors(self) -> bool: + return len(self._per_interval_and_per_day_vectors) > 0 def has_vector_calculator_expressions(self) -> bool: return len(self._vector_calculator_expressions) > 0 @@ -85,16 +85,16 @@ def get_provider_vectors_df( self._provider_vectors, self._resampling_frequency, realizations ) - def create_interval_and_average_vectors_df( + def create_per_interval_and_per_day_vectors_df( self, realizations: Optional[Sequence[int]] = None, ) -> pd.DataFrame: - """Get dataframe with interval delta and average rate vector data for provided vectors. + """Get dataframe with interval delta and per day delta vector data for provided vectors. The returned dataframe contains columns with name of vector and corresponding interval delta - or average rate data. + or per day delta data. - Interval delta and average rate date is calculated with same sampling frequency as provider + Interval delta and per day delta date is calculated with same sampling frequency as provider is set with. I.e. resampling frequency is given for providers supporting resampling, otherwise sampling frequency is fixed. @@ -110,16 +110,16 @@ def create_interval_and_average_vectors_df( * Handle calculation of cumulative when raw data is added * See TODO in calculate_from_resampled_cumulative_vectors_df() """ - if not self.has_interval_and_average_vectors(): + if not self.has_per_interval_and_per_day_vectors(): raise ValueError( - f'Vector data handler for provider "{self._name}" has no interval delta ' - "and average rate vector names" + f'Vector data handler for provider "{self._name}" has no per interval ' + "or per day vector names" ) cumulative_vector_names = [ get_cumulative_vector_name(elm) - for elm in self._interval_and_average_vectors - if is_interval_or_average_vector(elm) + for elm in self._per_interval_and_per_day_vectors + if is_per_interval_or_per_day_vector(elm) ] cumulative_vector_names = list(sorted(set(cumulative_vector_names))) @@ -127,25 +127,25 @@ def create_interval_and_average_vectors_df( cumulative_vector_names, self._resampling_frequency, realizations ) - interval_and_average_vectors_df = pd.DataFrame() - for vector_name in self._interval_and_average_vectors: + per_interval_and_per_day_vectors_df = pd.DataFrame() + for vector_name in self._per_interval_and_per_day_vectors: cumulative_vector_name = get_cumulative_vector_name(vector_name) - interval_and_average_vector_df = ( + per_interval_or_per_day_vector_df = ( calculate_from_resampled_cumulative_vectors_df( vectors_df[["DATE", "REAL", cumulative_vector_name]], - as_rate_per_day=vector_name.startswith("AVG_"), + as_rate_per_day=vector_name.startswith("PER_DAY_"), ) ) - if interval_and_average_vectors_df.empty: - interval_and_average_vectors_df = interval_and_average_vector_df + if per_interval_and_per_day_vectors_df.empty: + per_interval_and_per_day_vectors_df = per_interval_or_per_day_vector_df else: - interval_and_average_vectors_df = pd.merge( - interval_and_average_vectors_df, - interval_and_average_vector_df, + per_interval_and_per_day_vectors_df = pd.merge( + per_interval_and_per_day_vectors_df, + per_interval_or_per_day_vector_df, how="inner", ) - return interval_and_average_vectors_df + return per_interval_and_per_day_vectors_df def create_calculated_vectors_df( self, realizations: Optional[Sequence[int]] = None diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py index 5c9e9639f..267c8f49c 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py @@ -13,7 +13,7 @@ def has_provider_vectors(self) -> bool: ... @abc.abstractmethod - def has_interval_and_average_vectors(self) -> bool: + def has_per_interval_and_per_day_vectors(self) -> bool: ... @abc.abstractmethod @@ -27,7 +27,7 @@ def get_provider_vectors_df( ... @abc.abstractmethod - def create_interval_and_average_vectors_df( + def create_per_interval_and_per_day_vectors_df( self, realizations: Optional[Sequence[int]] = None, ) -> pd.DataFrame: diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/create_vector_traces_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/create_vector_traces_utils.py index dc59bf1af..c41447dd6 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/create_vector_traces_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/create_vector_traces_utils.py @@ -18,7 +18,7 @@ ) from ..types import FanchartOptions, StatisticsOptions -from ..utils.from_timeseries_cumulatives import is_interval_or_average_vector +from ..utils.from_timeseries_cumulatives import is_per_interval_or_per_day_vector def create_vector_observation_traces( @@ -356,7 +356,7 @@ def render_hovertemplate(vector: str, sampling_frequency: Optional[Frequency]) - * vector: str - name of vector * sampling_frequency: Optional[Frequency] - sampling frequency for hovering data info """ - if is_interval_or_average_vector(vector) and sampling_frequency: + if is_per_interval_or_per_day_vector(vector) and sampling_frequency: if sampling_frequency in [Frequency.DAILY, Frequency.WEEKLY]: return "(%{x|%b} %{x|%-d}, %{x|%Y}, %{y})
" if sampling_frequency == Frequency.MONTHLY: diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py index b0727b8a0..d1b67f669 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py @@ -1,10 +1,13 @@ import datetime -from typing import Optional +from typing import Dict, Optional import numpy as np import pandas as pd from webviz_subsurface._providers import Frequency +from webviz_subsurface._abbreviations.reservoir_simulation import ( + simulation_vector_description, +) ################################################################################### # NOTE: This code is a copy and modification of @@ -17,23 +20,52 @@ ################################################################################### -def is_interval_or_average_vector(vector: str) -> bool: - return vector.startswith("AVG_") or vector.startswith("INTVL_") +def is_per_interval_or_per_day_vector(vector: str) -> bool: + return vector.startswith("PER_DAY_") or vector.startswith("PER_INTVL_") def get_cumulative_vector_name(vector: str) -> str: - if not is_interval_or_average_vector(vector): + if not is_per_interval_or_per_day_vector(vector): raise ValueError( f'Expected "{vector}" to be a vector calculated from cumulative!' ) - if vector.startswith("AVG_"): - return f"{vector[4:7] + vector[7:].replace('R', 'T', 1)}" - if vector.startswith("INTVL_"): - return vector.lstrip("INTVL_") + if vector.startswith("PER_DAY_"): + return vector.lstrip("PER_DAY_") + if vector.startswith("PER_INTVL_"): + return vector.lstrip("PER_INTVL_") raise ValueError(f"Expected {vector} to be a cumulative vector!") +def create_per_day_vector_name(vector: str) -> str: + return f"PER_DAY_{vector}" + + +def create_per_interval_vector_name(vector: str) -> str: + return f"PER_INTVL_{vector}" + + +def create_per_interval_or_per_day_vector_description( + vector: str, + user_defined_descriptions: Dict[str, str] = None, +) -> str: + if not is_per_interval_or_per_day_vector(vector): + raise ValueError( + f'Expected "{vector}" to be a vector calculated from cumulative!' + ) + if not user_defined_descriptions: + return simulation_vector_description(vector) + + cumulative_vector_base = get_cumulative_vector_name(vector).split(":")[0] + _description = user_defined_descriptions.get(cumulative_vector_base, None) + if _description and vector.startswith("PER_DAY_"): + return "Average " + _description + " Per day" + if _description and vector.startswith("PER_INTVL_"): + return "Interval " + _description + + return simulation_vector_description(vector) + + def calculate_from_resampled_cumulative_vectors_df( vectors_df: pd.DataFrame, as_rate_per_day: bool, @@ -62,7 +94,7 @@ def calculate_from_resampled_cumulative_vectors_df( `TODO:` * IMPROVE FUNCTION NAME? * Handle raw data format? - * Give e.g. a dict with info of "avg and intvl" calculation for each vector column? + * Give e.g. a dict with info of "per_day and per_intvl" calculation for each vector column? Can thereby calculate everything for provided vector columns and no iterate column per column? """ @@ -86,7 +118,9 @@ def calculate_from_resampled_cumulative_vectors_df( vectors_df.reset_index(level=["REAL"], inplace=True) cumulative_name_map = { - vector: rename_vector_from_cumulative(vector, as_rate_per_day) + vector: create_per_day_vector_name(vector) + if as_rate_per_day + else create_per_interval_vector_name(vector) for vector in column_keys } cumulative_vectors = list(cumulative_name_map.values()) @@ -131,23 +165,6 @@ def calculate_from_resampled_cumulative_vectors_df( return cumulative_vectors_df -def rename_vector_from_cumulative(vector: str, as_rate: bool) -> str: - """This function assumes that it is a cumulative/total vector named in the Eclipse standard - and is fairly naive when converting to rate. Based in the list in libecl - https://github.com/equinor/libecl/blob/69f1ee0ddf696c87b6d85eca37eed7e8b66ac2db/\ - lib/ecl/smspec_node.cpp#L531-L586 - the T identifying total/cumulative should not occur before letter 4, - as all the listed strings are prefixed with one or two letters in the vectors. - Therefore starting the replace at the position 3 (4th letter) to reduce risk of errors - in the conversion to rate naming, but hard to be completely safe. - """ - return ( - f"AVG_{vector[0:3] + vector[3:].replace('T', 'R', 1)}" - if as_rate - else f"INTVL_{vector}" - ) - - # pylint: disable=too-many-return-statements def datetime_to_intervalstr(date: datetime.datetime, freq: Frequency) -> Optional[str]: if date is None: diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py index 79f9eb3e8..c97aa2b53 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py @@ -4,14 +4,16 @@ simulation_unit_reformat, simulation_vector_description, ) +from webviz_subsurface._providers import Frequency from webviz_subsurface._utils.vector_calculator import ( ExpressionInfo, VectorCalculator, get_expression_from_name, ) from webviz_subsurface.plugins._simulation_time_series.utils.from_timeseries_cumulatives import ( + create_per_interval_or_per_day_vector_description, get_cumulative_vector_name, - is_interval_or_average_vector, + is_per_interval_or_per_day_vector, ) from ..types import ProviderSet @@ -21,6 +23,7 @@ def create_vector_plot_titles_from_provider_set( vector_names: List[str], expressions: List[ExpressionInfo], provider_set: ProviderSet, + resampling_frequency: Optional[Frequency], user_defined_vector_descriptions: Dict[str, str], ) -> Dict[str, str]: """Create plot titles for vectors @@ -46,32 +49,23 @@ def create_vector_plot_titles_from_provider_set( title += f" [{simulation_unit_reformat(metadata.unit)}]" vector_title_dict[vector_name] = title - # INTVL_ or AVG_ vector - elif is_interval_or_average_vector(vector_name): - vector = vector_name - cumulative_vector = get_cumulative_vector_name(vector_name) - title = "" - if vector.startswith("AVG_"): - vector = vector.lstrip("AVG_") - title = user_defined_vector_descriptions.get( - cumulative_vector.split(":")[0], "" - ) - title = ( - title + "Per Day" - if title - else simulation_vector_description(vector) - ) - - if vector.startswith("INTVL_"): - vector = vector.lstrip("INTVL_") - title = user_defined_vector_descriptions.get( - cumulative_vector.split(":")[0], - simulation_vector_description(vector), - ) + # Per Interval or Per Day vector + elif is_per_interval_or_per_day_vector(vector_name): + title = create_per_interval_or_per_day_vector_description( + vector_name, user_defined_vector_descriptions + ) - metadata = provider_set.vector_metadata(vector) - if metadata and metadata.unit: - title += f" [{simulation_unit_reformat(metadata.unit)}]" + cumulative_vector = get_cumulative_vector_name(vector_name) + metadata = provider_set.vector_metadata(cumulative_vector) + if resampling_frequency: + title = f"{str(resampling_frequency.value).capitalize()} " + title + if vector_name.startswith("PER_DAY_"): + if metadata and metadata.unit: + _unit = metadata.unit + "/DAY" + title += f" [{simulation_unit_reformat(_unit)}]" + if vector_name.startswith("PER_INTVL_"): + if metadata and metadata.unit: + title += f" [{simulation_unit_reformat(metadata.unit)}]" vector_title_dict[vector_name] = title # Calculated vector diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/trace_line_shape.py b/webviz_subsurface/plugins/_simulation_time_series/utils/trace_line_shape.py index d1901daf0..9c39940b0 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/trace_line_shape.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/trace_line_shape.py @@ -2,7 +2,7 @@ from webviz_subsurface._providers import VectorMetadata -from .from_timeseries_cumulatives import is_interval_or_average_vector +from .from_timeseries_cumulatives import is_per_interval_or_per_day_vector def get_simulation_line_shape( @@ -11,7 +11,7 @@ def get_simulation_line_shape( vector_metadata: Optional[VectorMetadata] = None, ) -> str: """Get simulation time series line shape based on vector metadata""" - if is_interval_or_average_vector(vector): + if is_per_interval_or_per_day_vector(vector): # These custom calculated vectors are valid forwards in time. return "hv" From facffa632be3328ee46cca995dea4e50e080280d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Wed, 26 Jan 2022 15:42:22 +0100 Subject: [PATCH 03/27] isort --- .../_simulation_time_series/_plugin.py | 31 ++++++++----------- .../utils/from_timeseries_cumulatives.py | 2 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 080e1e1f7..1303a32bc 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -5,26 +5,32 @@ import dash import webviz_core_components as wcc +import webviz_subsurface_components as wsc from webviz_config import WebvizPluginABC, WebvizSettings from webviz_config.webviz_assets import WEBVIZ_ASSETS -import webviz_subsurface_components as wsc - import webviz_subsurface -from webviz_subsurface._abbreviations.reservoir_simulation import ( - historical_vector, -) +from webviz_subsurface._abbreviations.reservoir_simulation import historical_vector from webviz_subsurface._providers import Frequency from webviz_subsurface._utils.simulation_timeseries import ( check_and_format_observations, set_simulation_line_shape_fallback, ) +from webviz_subsurface._utils.user_defined_vector_definitions import ( + UserDefinedVectorDefinition, + create_user_defined_vector_description_dict, + create_user_defined_vector_descriptions_from_config, +) from webviz_subsurface._utils.vector_calculator import ( add_expressions_to_vector_selector_data, expressions_from_config, + get_custom_vector_definitions_from_expressions, validate_predefined_expression, ) -from webviz_subsurface._utils.vector_selector import add_vector_to_vector_selector_data +from webviz_subsurface._utils.vector_selector import ( + add_vector_to_vector_selector_data, + create_custom_vector_definition_from_user_defined_vector_data, +) from webviz_subsurface._utils.webvizstore_functions import get_path from ._callbacks import plugin_callbacks @@ -35,22 +41,11 @@ create_presampled_provider_set_from_paths, ) from .utils.from_timeseries_cumulatives import ( - create_per_interval_or_per_day_vector_description, create_per_day_vector_name, + create_per_interval_or_per_day_vector_description, create_per_interval_vector_name, ) -from webviz_subsurface._utils.vector_calculator import ( - get_custom_vector_definitions_from_expressions, -) -from webviz_subsurface._utils.vector_selector import ( - create_custom_vector_definition_from_user_defined_vector_data, -) -from webviz_subsurface._utils.user_defined_vector_definitions import ( - create_user_defined_vector_descriptions_from_config, - create_user_defined_vector_description_dict, - UserDefinedVectorDefinition, -) # pylint: disable=too-many-instance-attributes class SimulationTimeSeries(WebvizPluginABC): diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py index d1b67f669..81ea26580 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py @@ -4,10 +4,10 @@ import numpy as np import pandas as pd -from webviz_subsurface._providers import Frequency from webviz_subsurface._abbreviations.reservoir_simulation import ( simulation_vector_description, ) +from webviz_subsurface._providers import Frequency ################################################################################### # NOTE: This code is a copy and modification of From 22a791665e1bd4ab463f15546f874cef16d5e239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Thu, 27 Jan 2022 11:12:07 +0100 Subject: [PATCH 04/27] Add utils, typehint and correct naming --- webviz_subsurface/_utils/vector_calculator.py | 7 +-- webviz_subsurface/_utils/vector_selector.py | 35 ++++++++++++-- .../_simulation_time_series/_plugin.py | 46 +++++++++++-------- ...ed_delta_ensemble_vectors_accessor_impl.py | 2 +- .../derived_ensemble_vectors_accessor_impl.py | 2 +- .../utils/from_timeseries_cumulatives.py | 8 ++-- 6 files changed, 68 insertions(+), 32 deletions(-) diff --git a/webviz_subsurface/_utils/vector_calculator.py b/webviz_subsurface/_utils/vector_calculator.py index 4de7f7cc4..c4881019f 100644 --- a/webviz_subsurface/_utils/vector_calculator.py +++ b/webviz_subsurface/_utils/vector_calculator.py @@ -19,6 +19,7 @@ from .vector_selector import ( add_vector_to_vector_selector_data, is_vector_name_in_vector_selector_data, + CustomVectorDefinition, ) if sys.version_info >= (3, 8): @@ -208,7 +209,7 @@ def get_expression_from_name( def get_custom_vector_definitions_from_expressions( expressions: List[ExpressionInfo], -) -> dict: +) -> Dict[str, CustomVectorDefinition]: """ Get custom vector definitions for vector selector from list of calculated expressions. @@ -225,7 +226,7 @@ def get_custom_vector_definitions_from_expressions( Uses expression str as description if optional expression description str does not exist. """ - output = {} + output: Dict[str, CustomVectorDefinition] = {} for expression in expressions: name = expression["name"] key = name.split(":")[0] @@ -235,7 +236,7 @@ def get_custom_vector_definitions_from_expressions( if not "description" in expression else expression["description"] ) - output[key] = {"type": vector_type, "description": description} + output[key] = CustomVectorDefinition(type=vector_type, description=description) return output diff --git a/webviz_subsurface/_utils/vector_selector.py b/webviz_subsurface/_utils/vector_selector.py index aed916575..2bbc8f807 100644 --- a/webviz_subsurface/_utils/vector_selector.py +++ b/webviz_subsurface/_utils/vector_selector.py @@ -1,9 +1,22 @@ +import sys + from typing import Dict, Optional from webviz_subsurface._utils.user_defined_vector_definitions import ( UserDefinedVectorDefinition, ) +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict + + +class CustomVectorDefinition(TypedDict): + + description: str + type: str + def add_vector_to_vector_selector_data( vector_selector_data: list, @@ -55,10 +68,15 @@ def is_vector_name_in_vector_selector_data( return found -def create_custom_vector_definition_from_user_defined_vector_data( +def create_custom_vector_definitions_from_user_defined_vector_definitions( user_defined_vector_data: Dict[str, UserDefinedVectorDefinition] -) -> dict: - output: dict = {} +) -> Dict[str, CustomVectorDefinition]: + """Create dict of custom vector definitions for VectorSelector from dict + of user defined vector definitions dataclass objects from config + + Note: If type is not existing in config, type is set to "others" + """ + output: Dict[str, CustomVectorDefinition] = {} for elm in user_defined_vector_data: _type = user_defined_vector_data[elm].type output[elm] = { @@ -66,3 +84,14 @@ def create_custom_vector_definition_from_user_defined_vector_data( "type": _type if _type is not None else "others", } return output + + +def add_vector_definition_to_vector_definitions( + vector: str, + definition: CustomVectorDefinition, + custom_vector_definitions: Dict[str, CustomVectorDefinition], +) -> None: + """Add vector definition to dict of existing vector definitions""" + vector_base = vector.split(":")[0] + if vector_base not in custom_vector_definitions: + custom_vector_definitions[vector_base] = definition diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 1303a32bc..fdcc8d192 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -28,8 +28,10 @@ validate_predefined_expression, ) from webviz_subsurface._utils.vector_selector import ( + CustomVectorDefinition, + add_vector_definition_to_vector_definitions, add_vector_to_vector_selector_data, - create_custom_vector_definition_from_user_defined_vector_data, + create_custom_vector_definitions_from_user_defined_vector_definitions, ) from webviz_subsurface._utils.webvizstore_functions import get_path @@ -97,7 +99,7 @@ def __init__( ) ) self._custom_vector_definitions = ( - create_custom_vector_definition_from_user_defined_vector_data( + create_custom_vector_definitions_from_user_defined_vector_definitions( _user_defined_vector_definitions ) ) @@ -116,7 +118,7 @@ def __init__( self._presampled_frequency = None # TODO: Update functionality when allowing raw data and csv file input - # NOTE: If csv is implemented-> handle/disable statistics, INTVL_, AVG_, delta + # NOTE: If csv is implemented-> handle/disable statistics, PER_INTVL_, PER_DAY_, delta # ensemble, etc. if ensembles is not None: ensemble_paths: Dict[str, Path] = { @@ -199,22 +201,26 @@ def __init__( vector_base = vector.split(":")[0] _definition = wsc.VectorDefinitions.get(vector_base, None) _type = _definition["type"] if _definition else "others" - per_day_vec_base = per_day_vec.split(":")[0] - per_intvl_vec_base = per_intvl_vec.split(":")[0] - if per_day_vec_base not in self._custom_vector_definitions: - self._custom_vector_definitions[per_day_vec_base] = { - "type": _type, - "description": create_per_interval_or_per_day_vector_description( + add_vector_definition_to_vector_definitions( + per_day_vec, + CustomVectorDefinition( + type=_type, + description=create_per_interval_or_per_day_vector_description( per_day_vec, self._user_defined_vector_descriptions ), - } - if per_intvl_vec_base not in self._custom_vector_definitions: - self._custom_vector_definitions[per_intvl_vec_base] = { - "type": _type, - "description": create_per_interval_or_per_day_vector_description( - per_intvl_vec_base, self._user_defined_vector_descriptions + ), + custom_vector_definitions=self._custom_vector_definitions, + ) + add_vector_definition_to_vector_definitions( + per_intvl_vec, + CustomVectorDefinition( + type=_type, + description=create_per_interval_or_per_day_vector_description( + per_intvl_vec, self._user_defined_vector_descriptions ), - } + ), + custom_vector_definitions=self._custom_vector_definitions, + ) # Retreive predefined expressions from configuration and validate self._predefined_expressions_path = ( @@ -238,12 +244,12 @@ def __init__( expression["isValid"] = valid # Add expressions to custom vector definitions - _custom_vector_definitions_from_expressions = ( - get_custom_vector_definitions_from_expressions(self._predefined_expressions) - ) self._custom_vector_definitions_base = copy.deepcopy( self._custom_vector_definitions ) + _custom_vector_definitions_from_expressions = ( + get_custom_vector_definitions_from_expressions(self._predefined_expressions) + ) for key, value in _custom_vector_definitions_from_expressions.items(): if key not in self._custom_vector_definitions: self._custom_vector_definitions[key] = value @@ -362,7 +368,7 @@ def tour_steps(self) -> List[dict]: "content": ( "Display up to three different time series. " "Each time series will be visualized in a separate plot. " - "Vectors prefixed with AVG_ and INTVL_ are calculated in the fly " + "Vectors prefixed with PER_DAY_ and PER_INTVL_ are calculated in the fly " "from cumulative vectors, providing average rates and interval cumulatives " "over a time interval from the selected resampling frequency. Vectors " "categorized as calculated are created using the Vector Calculator below." diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py index 0d7947822..dbcc2dd18 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py @@ -226,7 +226,7 @@ def create_per_interval_and_per_day_vectors_df( per_interval_or_per_day_vector_df = ( calculate_from_resampled_cumulative_vectors_df( vectors_df[["DATE", "REAL", cumulative_vector_name]], - as_rate_per_day=vector_name.startswith("PER_DAY_"), + as_per_day=vector_name.startswith("PER_DAY_"), ) ) if per_interval_and_per_day_vectors_df.empty: diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py index eab726701..dd4e8af4b 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py @@ -133,7 +133,7 @@ def create_per_interval_and_per_day_vectors_df( per_interval_or_per_day_vector_df = ( calculate_from_resampled_cumulative_vectors_df( vectors_df[["DATE", "REAL", cumulative_vector_name]], - as_rate_per_day=vector_name.startswith("PER_DAY_"), + as_per_day=vector_name.startswith("PER_DAY_"), ) ) if per_interval_and_per_day_vectors_df.empty: diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py index 81ea26580..5f1032177 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py @@ -68,10 +68,10 @@ def create_per_interval_or_per_day_vector_description( def calculate_from_resampled_cumulative_vectors_df( vectors_df: pd.DataFrame, - as_rate_per_day: bool, + as_per_day: bool, ) -> pd.DataFrame: """ - Calculates interval delta or average rate data for vector columns in provided dataframe. + Calculates interval delta or average per day data for vector columns in provided dataframe. This function assumes data is already resampled when retrieved with ensemble summary provider. @@ -119,7 +119,7 @@ def calculate_from_resampled_cumulative_vectors_df( cumulative_name_map = { vector: create_per_day_vector_name(vector) - if as_rate_per_day + if as_per_day else create_per_interval_vector_name(vector) for vector in column_keys } @@ -147,7 +147,7 @@ def calculate_from_resampled_cumulative_vectors_df( cumulative_vectors_df.reset_index(inplace=True) # Convert interval cumulative to daily average rate if requested - if as_rate_per_day: + if as_per_day: days = cumulative_vectors_df["DATE"].diff().shift(-1).dt.days.fillna(value=0) for vector in column_keys: with np.errstate(invalid="ignore"): From 645e33ce8549dade48ff7e5908791e96a38b7492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Thu, 27 Jan 2022 15:25:42 +0100 Subject: [PATCH 05/27] Refactor code and update unit tests - Update unit tests according to "PER_DAY_" and "PER_INTVL_" notation - Bug fix (missing well name in plot title) - Refactor methods --- ...ed_delta_ensemble_vectors_accessor_impl.py | 30 +++-- ..._derived_ensemble_vectors_accessor_impl.py | 38 +++--- .../test_create_vector_traces_utils.py | 14 +- .../test_from_timeseries_cumulatives.py | 125 ++++++++++-------- .../test_utils/test_provier_set_utils.py | 43 +++++- .../test_utils/test_trace_line_shape.py | 4 +- .../_abbreviations/reservoir_simulation.py | 63 +++++---- .../_utils/user_defined_vector_definitions.py | 34 ++--- webviz_subsurface/_utils/vector_calculator.py | 12 +- webviz_subsurface/_utils/vector_selector.py | 48 +------ .../_reservoir_simulation_timeseries.py | 8 +- .../_simulation_time_series/_callbacks.py | 14 +- .../_simulation_time_series/_plugin.py | 71 ++++------ .../utils/from_timeseries_cumulatives.py | 26 +--- .../utils/provider_set_utils.py | 14 +- 15 files changed, 256 insertions(+), 288 deletions(-) diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_delta_ensemble_vectors_accessor_impl.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_delta_ensemble_vectors_accessor_impl.py index a5e61b7d3..3db2cd210 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_delta_ensemble_vectors_accessor_impl.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_delta_ensemble_vectors_accessor_impl.py @@ -76,9 +76,9 @@ ] ) -# INTVL_ calc for col "B" of Delta +# PER_INTVL_ calc for col "B" of Delta EXPECTED_DELTA_INVTL_DF = pd.DataFrame( - columns = ["DATE", "REAL", "INTVL_B"], + columns = ["DATE", "REAL", "PER_INTVL_B"], data = [ [datetime.datetime(2000,1,1), 1, 450.0 ], [datetime.datetime(2000,2,1), 1, 450.0 ], @@ -160,7 +160,7 @@ EnsembleSummaryProviderMock(INPUT_A_DF), EnsembleSummaryProviderMock(INPUT_B_DF), ), - vectors=["A", "B", "INTVL_B", "Sum A and B"], + vectors=["A", "B", "PER_INTVL_B", "Sum A and B"], expressions=[TEST_EXPRESSION], resampling_frequency=None, ) @@ -171,7 +171,7 @@ EnsembleSummaryProviderMock(INPUT_A_AFTER_2262_DF), EnsembleSummaryProviderMock(INPUT_B_AFTER_2262_DF), ), - vectors=["A", "B", "INTVL_B", "Sum A and B"], + vectors=["A", "B", "PER_INTVL_B", "Sum A and B"], expressions=[TEST_EXPRESSION], resampling_frequency=None, ) @@ -182,7 +182,7 @@ EnsembleSummaryProviderMock(pd.DataFrame()), EnsembleSummaryProviderMock(pd.DataFrame()), ), - vectors=["A", "B", "INTVL_B", "Sum A and B"], + vectors=["A", "B", "PER_INTVL_B", "Sum A and B"], expressions=None, resampling_frequency=None, ) @@ -204,7 +204,7 @@ pytest.param(TEST_ACCESSOR, EXPECTED_DELTA_DF), pytest.param(TEST_AFTER_2262_ACCESSOR, EXPECTED_DELTA_AFTER_2262_DF), ] -TEST_CREATE_INTVL_AVG_VECTOR_CASES = [ +TEST_CREATE_PER_INTVL_PER_DAY_VECTOR_CASES = [ pytest.param(TEST_ACCESSOR, EXPECTED_DELTA_INVTL_DF), pytest.param(TEST_AFTER_2262_ACCESSOR, EXPECTED_DELTA_INVTL_AFTER_2262_DF), ] @@ -222,10 +222,10 @@ def test_has_provider_vectors( @pytest.mark.parametrize("test_accessor, expected_state", TEST_STATUS_CASES) -def test_has_interval_and_average_vectors( +def test_has_per_interval_and_per_day_vectors( test_accessor: DerivedDeltaEnsembleVectorsAccessorImpl, expected_state: bool ) -> None: - assert test_accessor.has_interval_and_average_vectors() == expected_state + assert test_accessor.has_per_interval_and_per_day_vectors() == expected_state @pytest.mark.parametrize("test_accessor, expected_state", TEST_STATUS_CASES) @@ -260,20 +260,20 @@ def test_get_provider_vectors_filter_realizations( @pytest.mark.parametrize( - "test_accessor, expected_df", TEST_CREATE_INTVL_AVG_VECTOR_CASES + "test_accessor, expected_df", TEST_CREATE_PER_INTVL_PER_DAY_VECTOR_CASES ) -def test_create_interval_and_average_vectors_df( +def test_create_per_interval_and_per_day_vectors_df( test_accessor: DerivedDeltaEnsembleVectorsAccessorImpl, expected_df: pd.DataFrame ) -> None: assert_frame_equal( - expected_df, test_accessor.create_interval_and_average_vectors_df() + expected_df, test_accessor.create_per_interval_and_per_day_vectors_df() ) @pytest.mark.parametrize( - "test_accessor, expected_df", TEST_CREATE_INTVL_AVG_VECTOR_CASES + "test_accessor, expected_df", TEST_CREATE_PER_INTVL_PER_DAY_VECTOR_CASES ) -def test_create_interval_and_average_vectors_df_filter_realizations( +def test_create_per_interval_and_per_day_vectors_df_filter_realizations( test_accessor: DerivedDeltaEnsembleVectorsAccessorImpl, expected_df: pd.DataFrame ) -> None: # Filter realizations @@ -283,7 +283,9 @@ def test_create_interval_and_average_vectors_df_filter_realizations( .drop("index", axis=1) ) - test_df = test_accessor.create_interval_and_average_vectors_df(realizations=[1, 4]) + test_df = test_accessor.create_per_interval_and_per_day_vectors_df( + realizations=[1, 4] + ) assert_frame_equal(expected_reals_df, test_df) assert list(set(test_df["REAL"].values)) == [1, 4] diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_ensemble_vectors_accessor_impl.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_ensemble_vectors_accessor_impl.py index 0522787dd..84cef6210 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_ensemble_vectors_accessor_impl.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_ensemble_vectors_accessor_impl.py @@ -43,9 +43,9 @@ ] ) -# INTVL_ calc for col "B" input df -EXPECTED_INTVL_DF = pd.DataFrame( - columns = ["DATE", "REAL", "INTVL_B"], +# PER_INTVL_ calc for col "B" input df +EXPECTED_PER_INTVL_DF = pd.DataFrame( + columns = ["DATE", "REAL", "PER_INTVL_B"], data = [ [datetime.datetime(2000,1,1), 1, 50.0 ], [datetime.datetime(2000,2,1), 1, 50.0 ], @@ -75,7 +75,7 @@ ] ) make_date_column_datetime_object(INPUT_DF) -make_date_column_datetime_object(EXPECTED_INTVL_DF) +make_date_column_datetime_object(EXPECTED_PER_INTVL_DF) make_date_column_datetime_object(EXPECTED_SUM_A_AND_B_DF) # Dates AFTER year 2262! @@ -96,7 +96,7 @@ # no need to make date column datetime object INPUT_AFTER_2262_DF = INPUT_DF.copy() INPUT_AFTER_2262_DF["DATE"] = AFTER_2262_DATES -EXPECTED_INVTL_AFTER_2262_DF = EXPECTED_INTVL_DF.copy() +EXPECTED_INVTL_AFTER_2262_DF = EXPECTED_PER_INTVL_DF.copy() EXPECTED_INVTL_AFTER_2262_DF["DATE"] = AFTER_2262_DATES EXPECTED_SUM_A_AND_B_AFTER_2262_DF = EXPECTED_SUM_A_AND_B_DF.copy() EXPECTED_SUM_A_AND_B_AFTER_2262_DF["DATE"] = AFTER_2262_DATES @@ -117,7 +117,7 @@ TEST_ACCESSOR = DerivedEnsembleVectorsAccessorImpl( name="Test accessor", provider=EnsembleSummaryProviderMock(INPUT_DF), - vectors=["A", "B", "INTVL_B", "Sum A and B"], + vectors=["A", "B", "PER_INTVL_B", "Sum A and B"], expressions=[TEST_EXPRESSION], resampling_frequency=None, ) @@ -125,7 +125,7 @@ TEST_AFTER_2262_ACCESSOR = DerivedEnsembleVectorsAccessorImpl( name="Test after 2262 accessor", provider=EnsembleSummaryProviderMock(INPUT_AFTER_2262_DF), - vectors=["A", "B", "INTVL_B", "Sum A and B"], + vectors=["A", "B", "PER_INTVL_B", "Sum A and B"], expressions=[TEST_EXPRESSION], resampling_frequency=None, ) @@ -133,7 +133,7 @@ TEST_EMPTY_ACCESSOR = DerivedEnsembleVectorsAccessorImpl( name="Empty provider accessor", provider=EnsembleSummaryProviderMock(pd.DataFrame()), - vectors=["A", "B", "INTVL_B", "Sum A and B"], + vectors=["A", "B", "PER_INTVL_B", "Sum A and B"], expressions=None, resampling_frequency=None, ) @@ -156,8 +156,8 @@ pytest.param(TEST_ACCESSOR, INPUT_DF), pytest.param(TEST_AFTER_2262_ACCESSOR, INPUT_AFTER_2262_DF), ] -TEST_CREATE_INTVL_AVG_VECTOR_CASES = [ - pytest.param(TEST_ACCESSOR, EXPECTED_INTVL_DF), +TEST_CREATE_PER_INTVL_PER_DAY_VECTOR_CASES = [ + pytest.param(TEST_ACCESSOR, EXPECTED_PER_INTVL_DF), pytest.param(TEST_AFTER_2262_ACCESSOR, EXPECTED_INVTL_AFTER_2262_DF), ] TEST_CREATE_CALCULATED_VECTOR_CASES = [ @@ -174,10 +174,10 @@ def test_has_provider_vectors( @pytest.mark.parametrize("test_accessor, expected_state", TEST_STATUS_CASES) -def test_has_interval_and_average_vectors( +def test_has_per_interval_and_per_day_vectors( test_accessor: DerivedEnsembleVectorsAccessorImpl, expected_state: bool ) -> None: - assert test_accessor.has_interval_and_average_vectors() == expected_state + assert test_accessor.has_per_interval_and_per_day_vectors() == expected_state @pytest.mark.parametrize("test_accessor, expected_state", TEST_STATUS_CASES) @@ -212,20 +212,20 @@ def test_get_provider_vectors_filter_realizations( @pytest.mark.parametrize( - "test_accessor, expected_df", TEST_CREATE_INTVL_AVG_VECTOR_CASES + "test_accessor, expected_df", TEST_CREATE_PER_INTVL_PER_DAY_VECTOR_CASES ) -def test_create_interval_and_average_vectors_df( +def test_create_per_interval_and_per_day_vectors_df( test_accessor: DerivedEnsembleVectorsAccessorImpl, expected_df: pd.DataFrame ) -> None: assert_frame_equal( - expected_df, test_accessor.create_interval_and_average_vectors_df() + expected_df, test_accessor.create_per_interval_and_per_day_vectors_df() ) @pytest.mark.parametrize( - "test_accessor, expected_df", TEST_CREATE_INTVL_AVG_VECTOR_CASES + "test_accessor, expected_df", TEST_CREATE_PER_INTVL_PER_DAY_VECTOR_CASES ) -def test_create_interval_and_average_vectors_df_filter_realizations( +def test_create_per_interval_and_per_day_vectors_df_filter_realizations( test_accessor: DerivedEnsembleVectorsAccessorImpl, expected_df: pd.DataFrame ) -> None: # Filter realizations @@ -235,7 +235,9 @@ def test_create_interval_and_average_vectors_df_filter_realizations( .drop("index", axis=1) ) - test_df = test_accessor.create_interval_and_average_vectors_df(realizations=[1, 2]) + test_df = test_accessor.create_per_interval_and_per_day_vectors_df( + realizations=[1, 2] + ) assert_frame_equal(expected_reals_df, test_df) assert list(set(test_df["REAL"].values)) == [1, 2] diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_create_vector_traces_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_create_vector_traces_utils.py index 79fbb1b59..63ad22eb7 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_create_vector_traces_utils.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_create_vector_traces_utils.py @@ -231,13 +231,13 @@ def test_render_hovertemplate() -> None: template_yearly = "(%{x|%Y}, %{y})
" template_default = "(%{x}, %{y})
" - # Test interval/average vector names - assert template_daily == render_hovertemplate("AVG_a", Frequency.DAILY) - assert template_weekly == render_hovertemplate("AVG_a", Frequency.WEEKLY) - assert template_monthly == render_hovertemplate("AVG_a", Frequency.MONTHLY) - assert template_quarterly == render_hovertemplate("AVG_a", Frequency.QUARTERLY) - assert template_yearly == render_hovertemplate("AVG_a", Frequency.YEARLY) - assert template_default == render_hovertemplate("AVG_a", None) + # Test PER_DAY_/PER_INTVL_ vector names + assert template_daily == render_hovertemplate("PER_DAY_a", Frequency.DAILY) + assert template_weekly == render_hovertemplate("PER_DAY_a", Frequency.WEEKLY) + assert template_monthly == render_hovertemplate("PER_DAY_a", Frequency.MONTHLY) + assert template_quarterly == render_hovertemplate("PER_DAY_a", Frequency.QUARTERLY) + assert template_yearly == render_hovertemplate("PER_DAY_a", Frequency.YEARLY) + assert template_default == render_hovertemplate("PER_DAY_a", None) # Test other vector names assert template_default == render_hovertemplate("Vector_a", Frequency.DAILY) diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py index e7492b9e3..687890751 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py @@ -10,8 +10,9 @@ calculate_from_resampled_cumulative_vectors_df, datetime_to_intervalstr, get_cumulative_vector_name, - is_interval_or_average_vector, - rename_vector_from_cumulative, + is_per_interval_or_per_day_vector, + create_per_day_vector_name, + create_per_interval_vector_name, ) # ******************************************************************* @@ -38,8 +39,8 @@ [datetime.datetime(2021, 1, 15), 4, 1400.0, 1350.0], ], ) -EXPECTED_INTVL_WEEKLY_DF = pd.DataFrame( - columns=["DATE", "REAL", "INTVL_A", "INTVL_B"], +EXPECTED_PER_INTVL_WEEKLY_DF = pd.DataFrame( + columns=["DATE", "REAL", "PER_INTVL_A", "PER_INTVL_B"], data=[ [datetime.datetime(2021, 1, 1), 1, 50.0, 250.0], [datetime.datetime(2021, 1, 8), 1, 50.0, 250.0], @@ -52,8 +53,8 @@ [datetime.datetime(2021, 1, 15), 4, 0.0, 0.0 ], ], ) -EXPECTED_AVG_WEEKLY_DF = pd.DataFrame( - columns=["DATE", "REAL", "AVG_A", "AVG_B"], +EXPECTED_PER_DAY_WEEKLY_DF = pd.DataFrame( + columns=["DATE", "REAL", "PER_DAY_A", "PER_DAY_B"], data=[ [datetime.datetime(2021, 1, 1), 1, 50.0/7.0, 250.0/7.0], [datetime.datetime(2021, 1, 8), 1, 50.0/7.0, 250.0/7.0], @@ -68,8 +69,8 @@ ) # Convert date columns to datetime.datetime make_date_column_datetime_object(INPUT_WEEKLY_DF) -make_date_column_datetime_object(EXPECTED_INTVL_WEEKLY_DF) -make_date_column_datetime_object(EXPECTED_AVG_WEEKLY_DF) +make_date_column_datetime_object(EXPECTED_PER_INTVL_WEEKLY_DF) +make_date_column_datetime_object(EXPECTED_PER_DAY_WEEKLY_DF) # Monthly frequency - rate per day implies divide on days in month INPUT_MONTHLY_DF = pd.DataFrame( @@ -86,8 +87,8 @@ [datetime.datetime(2021, 3, 1), 4, 1400.0, 1350.0], ], ) -EXPECTED_INTVL_MONTHLY_DF = pd.DataFrame( - columns=["DATE", "REAL", "INTVL_A", "INTVL_B"], +EXPECTED_PER_INTVL_MONTHLY_DF = pd.DataFrame( + columns=["DATE", "REAL", "PER_INTVL_A", "PER_INTVL_B"], data=[ [datetime.datetime(2021, 1, 1), 1, 50.0, 250.0], [datetime.datetime(2021, 2, 1), 1, 50.0, 250.0], @@ -100,8 +101,8 @@ [datetime.datetime(2021, 3, 1), 4, 0.0, 0.0 ], ], ) -EXPECTED_AVG_MONTHLY_DF = pd.DataFrame( - columns=["DATE", "REAL", "AVG_A", "AVG_B"], +EXPECTED_PER_DAY_MONTHLY_DF = pd.DataFrame( + columns=["DATE", "REAL", "PER_DAY_A", "PER_DAY_B"], data=[ [datetime.datetime(2021, 1, 1), 1, 50.0/31.0, 250.0/31.0], [datetime.datetime(2021, 2, 1), 1, 50.0/28.0, 250.0/28.0], @@ -116,8 +117,8 @@ ) # Convert date columns to datetime.datetime make_date_column_datetime_object(INPUT_MONTHLY_DF) -make_date_column_datetime_object(EXPECTED_INTVL_MONTHLY_DF) -make_date_column_datetime_object(EXPECTED_AVG_MONTHLY_DF) +make_date_column_datetime_object(EXPECTED_PER_INTVL_MONTHLY_DF) +make_date_column_datetime_object(EXPECTED_PER_DAY_MONTHLY_DF) # Yearly frequency - rate per day implies divide on days in year INPUT_YEARLY_DF = pd.DataFrame( @@ -134,8 +135,8 @@ [datetime.datetime(2023, 1, 1), 4, 1400.0, 1350.0], ], ) -EXPECTED_INTVL_YEARLY_DF = pd.DataFrame( - columns=["DATE", "REAL", "INTVL_A", "INTVL_B"], +EXPECTED_PER_INTVL_YEARLY_DF = pd.DataFrame( + columns=["DATE", "REAL", "PER_INTVL_A", "PER_INTVL_B"], data=[ [datetime.datetime(2021, 1, 1), 1, 50.0, 250.0], [datetime.datetime(2022, 1, 1), 1, 50.0, 250.0], @@ -148,8 +149,8 @@ [datetime.datetime(2023, 1, 1), 4, 0.0, 0.0 ], ], ) -EXPECTED_AVG_YEARLY_DF = pd.DataFrame( - columns=["DATE", "REAL", "AVG_A", "AVG_B"], +EXPECTED_PER_DAY_YEARLY_DF = pd.DataFrame( + columns=["DATE", "REAL", "PER_DAY_A", "PER_DAY_B"], data=[ [datetime.datetime(2021, 1, 1), 1, 50.0/365.0, 250.0/365.0], [datetime.datetime(2022, 1, 1), 1, 50.0/365.0, 250.0/365.0], @@ -164,8 +165,8 @@ ) # Convert date columns to datetime.datetime make_date_column_datetime_object(INPUT_YEARLY_DF) -make_date_column_datetime_object(EXPECTED_INTVL_YEARLY_DF) -make_date_column_datetime_object(EXPECTED_AVG_YEARLY_DF) +make_date_column_datetime_object(EXPECTED_PER_INTVL_YEARLY_DF) +make_date_column_datetime_object(EXPECTED_PER_DAY_YEARLY_DF) # Monthly frequency after year 2262 - rate per day implies divide on days in month @@ -185,10 +186,10 @@ # NOTE: datetime.datetime after year 2262 is not converted to pd.Timestamp! INPUT_MONTHLY_AFTER_2262_DF = INPUT_MONTHLY_DF.copy() INPUT_MONTHLY_AFTER_2262_DF["DATE"] = AFTER_2262_MONTHLY_DATES -EXPECTED_INTVL_MONTHLY_AFTER_2262_DF = EXPECTED_INTVL_MONTHLY_DF.copy() -EXPECTED_INTVL_MONTHLY_AFTER_2262_DF["DATE"] = AFTER_2262_MONTHLY_DATES -EXPECTED_AVG_MONTHLY_AFTER_2262_DF = EXPECTED_AVG_MONTHLY_DF.copy() -EXPECTED_AVG_MONTHLY_AFTER_2262_DF["DATE"] = AFTER_2262_MONTHLY_DATES +EXPECTED_PER_INTVL_MONTHLY_AFTER_2262_DF = EXPECTED_PER_INTVL_MONTHLY_DF.copy() +EXPECTED_PER_INTVL_MONTHLY_AFTER_2262_DF["DATE"] = AFTER_2262_MONTHLY_DATES +EXPECTED_PER_DAY_MONTHLY_AFTER_2262_DF = EXPECTED_PER_DAY_MONTHLY_DF.copy() +EXPECTED_PER_DAY_MONTHLY_AFTER_2262_DF["DATE"] = AFTER_2262_MONTHLY_DATES # fmt: on @@ -202,33 +203,43 @@ # ******************************************************************* TEST_CASES = [ - pytest.param(INPUT_WEEKLY_DF, EXPECTED_INTVL_WEEKLY_DF, EXPECTED_AVG_WEEKLY_DF), - pytest.param(INPUT_MONTHLY_DF, EXPECTED_INTVL_MONTHLY_DF, EXPECTED_AVG_MONTHLY_DF), - pytest.param(INPUT_YEARLY_DF, EXPECTED_INTVL_YEARLY_DF, EXPECTED_AVG_YEARLY_DF), + pytest.param( + INPUT_WEEKLY_DF, EXPECTED_PER_INTVL_WEEKLY_DF, EXPECTED_PER_DAY_WEEKLY_DF + ), + pytest.param( + INPUT_MONTHLY_DF, EXPECTED_PER_INTVL_MONTHLY_DF, EXPECTED_PER_DAY_MONTHLY_DF + ), + pytest.param( + INPUT_YEARLY_DF, EXPECTED_PER_INTVL_YEARLY_DF, EXPECTED_PER_DAY_YEARLY_DF + ), pytest.param( INPUT_MONTHLY_AFTER_2262_DF, - EXPECTED_INTVL_MONTHLY_AFTER_2262_DF, - EXPECTED_AVG_MONTHLY_AFTER_2262_DF, + EXPECTED_PER_INTVL_MONTHLY_AFTER_2262_DF, + EXPECTED_PER_DAY_MONTHLY_AFTER_2262_DF, ), ] -@pytest.mark.parametrize("input_df, expected_intvl_df, expected_avg_df", TEST_CASES) +@pytest.mark.parametrize( + "input_df, expected_per_intvl_df, expected_per_day_df", TEST_CASES +) def test_calculate_from_resampled_cumulative_vectors_df( input_df: pd.DataFrame, - expected_intvl_df: pd.DataFrame, - expected_avg_df: pd.DataFrame, + expected_per_intvl_df: pd.DataFrame, + expected_per_day_df: pd.DataFrame, ) -> None: - # INTVL_ due to as_rate_per_day = False - calculated_intvl_df = calculate_from_resampled_cumulative_vectors_df( + # PER_INTVL_ due to as_rate_per_day = False + calculated_per_intvl_df = calculate_from_resampled_cumulative_vectors_df( input_df, False ) - # AVG_ due to as_rate_per_day = True - calculated_avg_df = calculate_from_resampled_cumulative_vectors_df(input_df, True) + # PER_DAY_ due to as_rate_per_day = True + calculated_per_day_df = calculate_from_resampled_cumulative_vectors_df( + input_df, True + ) - assert_frame_equal(expected_intvl_df, calculated_intvl_df) - assert_frame_equal(expected_avg_df, calculated_avg_df) + assert_frame_equal(expected_per_intvl_df, calculated_per_intvl_df) + assert_frame_equal(expected_per_day_df, calculated_per_day_df) def test_calculate_from_resampled_cumulative_vectors_df_invalid_input() -> None: @@ -256,22 +267,22 @@ def test_calculate_from_resampled_cumulative_vectors_df_invalid_input() -> None: ) -def test_is_interval_or_average_vector() -> None: - assert is_interval_or_average_vector("AVG_Vector") - assert is_interval_or_average_vector("INTVL_Vector") - assert not is_interval_or_average_vector("avg_Vector") - assert not is_interval_or_average_vector("intvl_Vector") - assert not is_interval_or_average_vector("vector") +def test_is_per_interval_or_per_day_vector() -> None: + assert is_per_interval_or_per_day_vector("PER_DAY_Vector") + assert is_per_interval_or_per_day_vector("PER_INTVL_Vector") + assert not is_per_interval_or_per_day_vector("per_day_Vector") + assert not is_per_interval_or_per_day_vector("per_intvl_Vector") + assert not is_per_interval_or_per_day_vector("vector") def test_get_cumulative_vector_name() -> None: - assert get_cumulative_vector_name("AVG_FOPT") == "FOPT" - assert get_cumulative_vector_name("INTVL_FOPT") == "FOPT" + assert get_cumulative_vector_name("PER_DAY_FOPT") == "FOPT" + assert get_cumulative_vector_name("PER_INTVL_FOPT") == "FOPT" - assert get_cumulative_vector_name("AVG_FOPR") == "FOPT" - assert get_cumulative_vector_name("INTVL_FOPR") == "FOPR" + assert get_cumulative_vector_name("PER_DAY_FOPR") == "FOPR" + assert get_cumulative_vector_name("PER_INTVL_FOPR") == "FOPR" - # Expect ValueError when verifying vector not starting with "AVG_" or "INTVL_" + # Expect ValueError when verifying vector not starting with "PER_DAY_" or "PER_INTVL_" try: get_cumulative_vector_name("Test_vector") pytest.fail('Expected retrieving of cumulative vector name for "Test_vector"') @@ -282,12 +293,16 @@ def test_get_cumulative_vector_name() -> None: ) -def test_rename_vector_from_cumulative() -> None: - assert rename_vector_from_cumulative("Vector", True) == "AVG_Vector" - assert rename_vector_from_cumulative("FOPT", True) == "AVG_FOPR" - assert rename_vector_from_cumulative("FOPS", True) == "AVG_FOPS" - assert rename_vector_from_cumulative("Vector", False) == "INTVL_Vector" - assert rename_vector_from_cumulative("FOPT", False) == "INTVL_FOPT" +def test_create_per_day_vector_name() -> None: + assert create_per_day_vector_name("Vector") == "PER_DAY_Vector" + assert create_per_day_vector_name("FOPT") == "PER_DAY_FOPT" + assert create_per_day_vector_name("FOPS") == "PER_DAY_FOPS" + + +def test_create_per_interval_vector_name() -> None: + assert create_per_interval_vector_name("Vector") == "PER_INTVL_Vector" + assert create_per_interval_vector_name("FOPT") == "PER_INTVL_FOPT" + assert create_per_interval_vector_name("FOPS") == "PER_INTVL_FOPS" def test_datetime_to_intervalstr() -> None: diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py index 22a9ce2c6..065a9e50c 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py @@ -1,6 +1,10 @@ from typing import List, Optional -from webviz_subsurface_components import ExpressionInfo, VariableVectorMapInfo +from webviz_subsurface_components import ( + ExpressionInfo, + VariableVectorMapInfo, + VectorDefinition, +) from webviz_subsurface._providers import VectorMetadata from webviz_subsurface.plugins._simulation_time_series.types.provider_set import ( @@ -36,13 +40,14 @@ class EnsembleSummaryProviderMock(EnsembleSummaryProviderDummy): ######################################## def vector_names(self) -> List[str]: """Return empty list only to allow constructing ProviderSet object""" - return ["FGIT", "WGOR:A1", "WBHP:A1"] + return ["FGIT", "WGOR:A1", "WBHP:A1", "WOPT:A1"] def realizations(self) -> List[int]: """Return empty list only to allow constructing ProviderSet object""" return [] def vector_metadata(self, vector_name: str) -> Optional[VectorMetadata]: + # NOTE: All vector names below must be defined in vector_names() method as well! if vector_name == "FGIT": return VectorMetadata( unit="unit_1", @@ -73,6 +78,16 @@ def vector_metadata(self, vector_name: str) -> Optional[VectorMetadata]: wgname=None, get_num=None, ) + if vector_name == "WOPT:A1": + return VectorMetadata( + unit="unit_4", + is_total=False, + is_rate=False, + is_historical=False, + keyword="A1", + wgname=None, + get_num=None, + ) return None @@ -163,15 +178,31 @@ def test_create_calculated_unit_from_provider_set() -> None: def test_create_vector_plot_titles_from_provider_set() -> None: - vector_names = ["FGIT", "WGOR:A1", "First Expression"] + vector_names = ["FGIT", "WGOR:A1", "PER_DAY_WOPT:A1", "First Expression"] expressions = [FIRST_TEST_EXPRESSION] - expected_titles = { + user_defined_definitions = { + "FGIT": VectorDefinition(description="First Test title", type="field"), + "WOPT": VectorDefinition(description="Second Test title", type="well"), + } + + first_expected_titles = { "FGIT": "Gas Injection Total [unit_1]", "WGOR:A1": "Gas-Oil Ratio, well A1 [unit_2]", + "PER_DAY_WOPT:A1": "Average Oil Production Total Per day, well A1 [unit_4/DAY]", + "First Expression": "First Expression [unit_1+unit_2]", + } + + second_expected_titles = { + "FGIT": "First Test title [unit_1]", + "WGOR:A1": "Gas-Oil Ratio, well A1 [unit_2]", + "PER_DAY_WOPT:A1": "Average Second Test title Per day, well A1 [unit_4/DAY]", "First Expression": "First Expression [unit_1+unit_2]", } - assert expected_titles == create_vector_plot_titles_from_provider_set( - vector_names, expressions, TEST_PROVIDER_SET + assert first_expected_titles == create_vector_plot_titles_from_provider_set( + vector_names, expressions, TEST_PROVIDER_SET, {} + ) + assert second_expected_titles == create_vector_plot_titles_from_provider_set( + vector_names, expressions, TEST_PROVIDER_SET, user_defined_definitions ) diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_trace_line_shape.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_trace_line_shape.py index 9bab562a8..0e02e6f17 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_trace_line_shape.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_trace_line_shape.py @@ -35,8 +35,8 @@ def test_get_simulation_line_shape() -> None: get_num=None, ) - assert get_simulation_line_shape("Fallback", "INTVL_vector", None) == "hv" - assert get_simulation_line_shape("Fallback", "AVG_vector", None) == "hv" + assert get_simulation_line_shape("Fallback", "PER_INTVL_vector", None) == "hv" + assert get_simulation_line_shape("Fallback", "PER_DAY_vector", None) == "hv" assert get_simulation_line_shape("Fallback", "test_vector", None) == "Fallback" assert ( get_simulation_line_shape("Fallback", "test_vector", total_vector_metadata) diff --git a/webviz_subsurface/_abbreviations/reservoir_simulation.py b/webviz_subsurface/_abbreviations/reservoir_simulation.py index ff0d4e07f..ecaf9b003 100644 --- a/webviz_subsurface/_abbreviations/reservoir_simulation.py +++ b/webviz_subsurface/_abbreviations/reservoir_simulation.py @@ -1,11 +1,11 @@ import json import pathlib import warnings -from typing import Optional, Tuple, cast +from typing import Dict, Optional, Tuple, cast import pandas as pd -from webviz_subsurface_components import VectorDefinitions +from webviz_subsurface_components import VectorDefinitions, VectorDefinition _DATA_PATH = pathlib.Path(__file__).parent.absolute() / "abbreviation_data" @@ -36,17 +36,23 @@ def simulation_vector_base(vector: str) -> str: return vector.split(":", 1)[0].split("_", 1)[0][:5] if ":" in vector else vector -def simulation_vector_description(vector: str) -> str: +def simulation_vector_description( + vector: str, + user_defined_vector_definitions: Optional[Dict[str, VectorDefinition]] = None, +) -> str: """Returns a more human friendly description of the simulation vector if possible, otherwise returns the input as is. - # TODO: Remove support for "AVG_" and "INTVL_" when all usage is deprecated + # TODO: Remove support for "AVG_" and "INTVL_" when all usage is deprecated. """ + prefix = "" + suffix = "" if vector.startswith("AVG_"): prefix = "Average " vector = vector[4:] elif vector.startswith("PER_DAY_"): prefix = "Average " + suffix = " Per day" vector = vector[8:] elif vector.startswith("INTVL_"): prefix = "Interval " @@ -54,8 +60,6 @@ def simulation_vector_description(vector: str) -> str: elif vector.startswith("PER_INTVL_"): prefix = "Interval " vector = vector[10:] - else: - prefix = "" vector_name: str node: Optional[str] @@ -65,6 +69,17 @@ def simulation_vector_description(vector: str) -> str: vector_name = vector node = None + def _get_vector_definition(vector: str) -> Optional[VectorDefinition]: + """Get vector definition. Fetch user defined if existing""" + if ( + user_defined_vector_definitions + and vector in user_defined_vector_definitions + ): + return user_defined_vector_definitions[vector] + if vector in SIMULATION_VECTOR_TERMINOLOGY: + return SIMULATION_VECTOR_TERMINOLOGY[vector] + return None + if len(vector_name) == 8: if vector_name[0] == "R": # Region vectors for other FIP regions than FIPNUM are written on a special form: @@ -72,34 +87,32 @@ def simulation_vector_description(vector: str) -> str: # E.g.: For an array "FIPREG": ROIP is ROIP_REG, RPR is RPR__REG and ROIPL is ROIPLREG # Underscores _ are always used to fill [vector_base_name, fip] = [vector_name[0:5].rstrip("_"), vector_name[5:]] - if ( - vector_base_name in SIMULATION_VECTOR_TERMINOLOGY - and SIMULATION_VECTOR_TERMINOLOGY[vector_base_name]["type"] == "region" - ): + + _definition = _get_vector_definition(vector_base_name) + if _definition and _definition["type"] == "region": return ( - f"{prefix}{SIMULATION_VECTOR_TERMINOLOGY[vector_base_name]['description']}" - f", region {fip} {node}" + f"{prefix}{_definition['description']}{suffix}, region {fip} {node}" ) elif vector_name.startswith("W") and vector_name[4] == "L": # These are completion vectors, e.g. WWCTL:__1:OP_1 and WOPRL_10:OP_1 for # water-cut in OP_1 completion 1 and oil production rate in OP_1 completion 10 [vector_base_name, comp] = [vector_name[0:5], vector_name[5:].lstrip("_")] - if ( - vector_base_name in SIMULATION_VECTOR_TERMINOLOGY - and SIMULATION_VECTOR_TERMINOLOGY[vector_base_name]["type"] - == "completion" - ): + _definition = _get_vector_definition(vector_base_name) + if _definition and _definition["type"] == "completion": return ( - f"{prefix}{SIMULATION_VECTOR_TERMINOLOGY[vector_base_name]['description']}" - f", well {node} completion {comp}" + f"{prefix}{_definition['description']}" + f"{suffix}, well {node} completion {comp}" ) - if vector_name in SIMULATION_VECTOR_TERMINOLOGY: - metadata = SIMULATION_VECTOR_TERMINOLOGY[vector_name] + _definition = _get_vector_definition(vector_name) + if _definition: if node is None: - return prefix + metadata["description"] - return f"{prefix}{metadata['description']}, {metadata['type'].replace('_', ' ')} {node}" + return prefix + _definition["description"] + suffix + return ( + f"{prefix}{_definition['description']}{suffix}, " + f"{_definition['type'].replace('_', ' ')} {node}" + ) if not vector.startswith( ("AU", "BU", "CU", "FU", "GU", "RU", "SU", "WU", "Recovery Factor of") @@ -110,11 +123,11 @@ def simulation_vector_description(vector: str) -> str: warnings.warn( ( f"Could not find description for vector {vector_name}. Consider adding" - " it in the GitHub repo https://github.com/equinor/webviz-subsurface?" + " it in the GitHub repo https://github.com/equinor/webviz-subsurface-components?" ), UserWarning, ) - return prefix + vector + return prefix + vector + suffix def historical_vector( diff --git a/webviz_subsurface/_utils/user_defined_vector_definitions.py b/webviz_subsurface/_utils/user_defined_vector_definitions.py index 705efe5db..255663136 100644 --- a/webviz_subsurface/_utils/user_defined_vector_definitions.py +++ b/webviz_subsurface/_utils/user_defined_vector_definitions.py @@ -1,11 +1,12 @@ import sys -from dataclasses import dataclass from pathlib import Path from typing import Dict, Optional import yaml +from webviz_subsurface_components import VectorDefinition + if sys.version_info >= (3, 8): from typing import TypedDict else: @@ -66,24 +67,18 @@ class ConfigUserDefinedVectorDefinition( type: str -@dataclass(frozen=True) -class UserDefinedVectorDefinition: - description: str - type: Optional[str] - - def create_user_defined_vector_descriptions_from_config( user_defined_vector_data_path: Optional[Path], -) -> Dict[str, UserDefinedVectorDefinition]: - """Create user defined vector data from config +) -> Dict[str, VectorDefinition]: + """Create user defined vector definitions from config `Input:` - Path for yaml-file containing user defined vector data + Path for yaml-file containing user defined vector definitions `Return:` - Dict with vector as name, and user defined vector data object as value. + Dict with vector as name, and user defined vector definition object as value. """ - output: Dict[str, UserDefinedVectorDefinition] = {} + output: Dict[str, VectorDefinition] = {} if user_defined_vector_data_path is None: return output @@ -94,18 +89,7 @@ def create_user_defined_vector_descriptions_from_config( for vector, vector_data in vector_data_dict.items(): _description = vector_data.get("description", "") - _type = vector_data.get("type", None) - output[vector] = UserDefinedVectorDefinition( - description=_description, type=_type - ) - - return output - + _type = vector_data.get("type", "others") + output[vector] = VectorDefinition(description=_description, type=_type) -def create_user_defined_vector_description_dict( - user_defined_vector_definitions: Dict[str, UserDefinedVectorDefinition] -) -> Dict[str, str]: - output: Dict[str, str] = {} - for elm in user_defined_vector_definitions: - output[elm] = user_defined_vector_definitions[elm].description return output diff --git a/webviz_subsurface/_utils/vector_calculator.py b/webviz_subsurface/_utils/vector_calculator.py index 0dcae6dc8..1f728309d 100644 --- a/webviz_subsurface/_utils/vector_calculator.py +++ b/webviz_subsurface/_utils/vector_calculator.py @@ -11,6 +11,7 @@ ExternalParseData, VariableVectorMapInfo, VectorCalculator, + VectorDefinition, ) from webviz_subsurface._providers import EnsembleSummaryProvider, Frequency @@ -18,7 +19,6 @@ from .vector_selector import ( add_vector_to_vector_selector_data, is_vector_name_in_vector_selector_data, - CustomVectorDefinition, ) if sys.version_info >= (3, 8): @@ -206,11 +206,11 @@ def get_expression_from_name( return None -def get_custom_vector_definitions_from_expressions( +def get_vector_definitions_from_expressions( expressions: List[ExpressionInfo], -) -> Dict[str, CustomVectorDefinition]: +) -> Dict[str, VectorDefinition]: """ - Get custom vector definitions for vector selector from list of calculated expressions. + Get vector definitions for vector selector from list of calculated expressions. VectorSelector has VectorDefinitions which is utilized for calculated expressions. @@ -225,7 +225,7 @@ def get_custom_vector_definitions_from_expressions( Uses expression str as description if optional expression description str does not exist. """ - output: Dict[str, CustomVectorDefinition] = {} + output: Dict[str, VectorDefinition] = {} for expression in expressions: name = expression["name"] key = name.split(":")[0] @@ -235,7 +235,7 @@ def get_custom_vector_definitions_from_expressions( if not "description" in expression else expression["description"] ) - output[key] = CustomVectorDefinition(type=vector_type, description=description) + output[key] = VectorDefinition(type=vector_type, description=description) return output diff --git a/webviz_subsurface/_utils/vector_selector.py b/webviz_subsurface/_utils/vector_selector.py index 2bbc8f807..05ae8681e 100644 --- a/webviz_subsurface/_utils/vector_selector.py +++ b/webviz_subsurface/_utils/vector_selector.py @@ -1,21 +1,4 @@ -import sys - -from typing import Dict, Optional - -from webviz_subsurface._utils.user_defined_vector_definitions import ( - UserDefinedVectorDefinition, -) - -if sys.version_info >= (3, 8): - from typing import TypedDict -else: - from typing_extensions import TypedDict - - -class CustomVectorDefinition(TypedDict): - - description: str - type: str +from typing import Optional def add_vector_to_vector_selector_data( @@ -66,32 +49,3 @@ def is_vector_name_in_vector_selector_data( if not found: return False return found - - -def create_custom_vector_definitions_from_user_defined_vector_definitions( - user_defined_vector_data: Dict[str, UserDefinedVectorDefinition] -) -> Dict[str, CustomVectorDefinition]: - """Create dict of custom vector definitions for VectorSelector from dict - of user defined vector definitions dataclass objects from config - - Note: If type is not existing in config, type is set to "others" - """ - output: Dict[str, CustomVectorDefinition] = {} - for elm in user_defined_vector_data: - _type = user_defined_vector_data[elm].type - output[elm] = { - "description": user_defined_vector_data[elm].description, - "type": _type if _type is not None else "others", - } - return output - - -def add_vector_definition_to_vector_definitions( - vector: str, - definition: CustomVectorDefinition, - custom_vector_definitions: Dict[str, CustomVectorDefinition], -) -> None: - """Add vector definition to dict of existing vector definitions""" - vector_base = vector.split(":")[0] - if vector_base not in custom_vector_definitions: - custom_vector_definitions[vector_base] = definition diff --git a/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py b/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py index 69e2718d6..43ac02040 100644 --- a/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py +++ b/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py @@ -59,7 +59,7 @@ expressions_from_config, get_calculated_units, get_calculated_vector_df, - get_custom_vector_definitions_from_expressions, + get_vector_definitions_from_expressions, get_expression_from_name, get_selected_expressions, validate_predefined_expression, @@ -629,7 +629,7 @@ def layout(self) -> wcc.FlexBox: numSecondsUntilSuggestionsAreShown=0.5, lineBreakAfterTag=True, customVectorDefinitions=( - get_custom_vector_definitions_from_expressions( + get_vector_definitions_from_expressions( self.predefined_expressions ) ), @@ -1231,8 +1231,8 @@ def _update_vector_calculator_expressions_actual( if new_selected_vectors == selected_vectors: new_selected_vectors = dash.no_update - new_custom_vector_definitions = ( - get_custom_vector_definitions_from_expressions(new_expressions) + new_custom_vector_definitions = get_vector_definitions_from_expressions( + new_expressions ) if new_custom_vector_definitions == custom_vector_definitions: diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index 5b53ddf8e..827b9ded2 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -8,15 +8,19 @@ from dash.exceptions import PreventUpdate from webviz_config import EncodedFile, WebvizPluginABC from webviz_config._theme_class import WebvizConfigTheme -from webviz_subsurface_components import ExpressionInfo, ExternalParseData +from webviz_subsurface_components import ( + ExpressionInfo, + ExternalParseData, + VectorDefinition, +) from webviz_subsurface._providers import Frequency from webviz_subsurface._utils.formatting import printable_int_list from webviz_subsurface._utils.unique_theming import unique_colors from webviz_subsurface._utils.vector_calculator import ( add_expressions_to_vector_selector_data, - get_custom_vector_definitions_from_expressions, get_selected_expressions, + get_vector_definitions_from_expressions, ) from webviz_subsurface._utils.vector_selector import ( is_vector_name_in_vector_selector_data, @@ -65,7 +69,7 @@ def plugin_callbacks( vector_selector_base_data: list, custom_vector_definitions_base: dict, observations: dict, # TODO: Improve typehint? - user_defined_vector_descriptions: Dict[str, str], + user_defined_vector_definitions: Dict[str, VectorDefinition], line_shape_fallback: str = "linear", ) -> None: # TODO: Consider adding: presampled_frequency: Optional[Frequency] argument for use when @@ -228,8 +232,8 @@ def _update_graph( vectors, selected_expressions, input_provider_set, + user_defined_vector_definitions, resampling_frequency, - user_defined_vector_descriptions, ) figure_builder = VectorSubplotBuilder( vectors, @@ -846,7 +850,7 @@ def _update_vector_calculator_expressions_on_modal_close( ) # Get new custom vector definitions - new_custom_vector_definitions = get_custom_vector_definitions_from_expressions( + new_custom_vector_definitions = get_vector_definitions_from_expressions( new_expressions ) for key, value in custom_vector_definitions_base.items(): diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index fdcc8d192..60fe50990 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -10,29 +10,25 @@ from webviz_config.webviz_assets import WEBVIZ_ASSETS import webviz_subsurface -from webviz_subsurface._abbreviations.reservoir_simulation import historical_vector +from webviz_subsurface._abbreviations.reservoir_simulation import ( + historical_vector, + simulation_vector_description, +) from webviz_subsurface._providers import Frequency from webviz_subsurface._utils.simulation_timeseries import ( check_and_format_observations, set_simulation_line_shape_fallback, ) from webviz_subsurface._utils.user_defined_vector_definitions import ( - UserDefinedVectorDefinition, - create_user_defined_vector_description_dict, create_user_defined_vector_descriptions_from_config, ) from webviz_subsurface._utils.vector_calculator import ( add_expressions_to_vector_selector_data, expressions_from_config, - get_custom_vector_definitions_from_expressions, + get_vector_definitions_from_expressions, validate_predefined_expression, ) -from webviz_subsurface._utils.vector_selector import ( - CustomVectorDefinition, - add_vector_definition_to_vector_definitions, - add_vector_to_vector_selector_data, - create_custom_vector_definitions_from_user_defined_vector_definitions, -) +from webviz_subsurface._utils.vector_selector import add_vector_to_vector_selector_data from webviz_subsurface._utils.webvizstore_functions import get_path from ._callbacks import plugin_callbacks @@ -44,7 +40,6 @@ ) from .utils.from_timeseries_cumulatives import ( create_per_day_vector_name, - create_per_interval_or_per_day_vector_description, create_per_interval_vector_name, ) @@ -85,23 +80,15 @@ def __init__( user_defined_vector_definitions ] ) - # Vector name as key, description data as value - _user_defined_vector_definitions: Dict[ - str, UserDefinedVectorDefinition + self._user_defined_vector_definitions: Dict[ + str, wsc.VectorDefinition ] = create_user_defined_vector_descriptions_from_config( get_path(self._user_defined_vector_descriptions_path) if self._user_defined_vector_descriptions_path else None ) - self._user_defined_vector_descriptions = ( - create_user_defined_vector_description_dict( - _user_defined_vector_definitions - ) - ) - self._custom_vector_definitions = ( - create_custom_vector_definitions_from_user_defined_vector_definitions( - _user_defined_vector_definitions - ) + self._custom_vector_definitions = copy.deepcopy( + self._user_defined_vector_definitions ) self._line_shape_fallback = set_simulation_line_shape_fallback( @@ -197,30 +184,30 @@ def __init__( ) # Add vector base to custom vector definition if not existing - # TODO: Make an util? vector_base = vector.split(":")[0] _definition = wsc.VectorDefinitions.get(vector_base, None) _type = _definition["type"] if _definition else "others" - add_vector_definition_to_vector_definitions( - per_day_vec, - CustomVectorDefinition( + + per_day_vec_base = per_day_vec.split(":")[0] + per_intvl_vec_base = per_intvl_vec.split(":")[0] + if per_day_vec_base not in self._custom_vector_definitions: + self._custom_vector_definitions[ + per_day_vec_base + ] = wsc.VectorDefinition( type=_type, - description=create_per_interval_or_per_day_vector_description( - per_day_vec, self._user_defined_vector_descriptions + description=simulation_vector_description( + per_day_vec_base, self._user_defined_vector_definitions ), - ), - custom_vector_definitions=self._custom_vector_definitions, - ) - add_vector_definition_to_vector_definitions( - per_intvl_vec, - CustomVectorDefinition( + ) + if per_intvl_vec_base not in self._custom_vector_definitions: + self._custom_vector_definitions[ + per_intvl_vec_base + ] = wsc.VectorDefinition( type=_type, - description=create_per_interval_or_per_day_vector_description( - per_intvl_vec, self._user_defined_vector_descriptions + description=simulation_vector_description( + per_intvl_vec_base, self._user_defined_vector_definitions ), - ), - custom_vector_definitions=self._custom_vector_definitions, - ) + ) # Retreive predefined expressions from configuration and validate self._predefined_expressions_path = ( @@ -248,7 +235,7 @@ def __init__( self._custom_vector_definitions ) _custom_vector_definitions_from_expressions = ( - get_custom_vector_definitions_from_expressions(self._predefined_expressions) + get_vector_definitions_from_expressions(self._predefined_expressions) ) for key, value in _custom_vector_definitions_from_expressions.items(): if key not in self._custom_vector_definitions: @@ -308,7 +295,7 @@ def set_callbacks(self, app: dash.Dash) -> None: vector_selector_base_data=self._vector_selector_base_data, custom_vector_definitions_base=self._custom_vector_definitions_base, observations=self._observations, - user_defined_vector_descriptions=self._user_defined_vector_descriptions, + user_defined_vector_definitions=self._user_defined_vector_definitions, line_shape_fallback=self._line_shape_fallback, ) diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py index a87608138..d1dc3221c 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/from_timeseries_cumulatives.py @@ -1,12 +1,9 @@ import datetime -from typing import Dict, Optional +from typing import Optional import numpy as np import pandas as pd -from webviz_subsurface._abbreviations.reservoir_simulation import ( - simulation_vector_description, -) from webviz_subsurface._providers import Frequency from webviz_subsurface._utils.dataframe_utils import ( assert_date_column_is_datetime_object, @@ -49,27 +46,6 @@ def create_per_interval_vector_name(vector: str) -> str: return f"PER_INTVL_{vector}" -def create_per_interval_or_per_day_vector_description( - vector: str, - user_defined_descriptions: Dict[str, str] = None, -) -> str: - if not is_per_interval_or_per_day_vector(vector): - raise ValueError( - f'Expected "{vector}" to be a vector calculated from cumulative!' - ) - if not user_defined_descriptions: - return simulation_vector_description(vector) - - cumulative_vector_base = get_cumulative_vector_name(vector).split(":")[0] - _description = user_defined_descriptions.get(cumulative_vector_base, None) - if _description and vector.startswith("PER_DAY_"): - return "Average " + _description + " Per day" - if _description and vector.startswith("PER_INTVL_"): - return "Interval " + _description - - return simulation_vector_description(vector) - - def calculate_from_resampled_cumulative_vectors_df( vectors_df: pd.DataFrame, as_per_day: bool, diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py index 3d9721106..7531b3ab3 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_set_utils.py @@ -1,5 +1,6 @@ from typing import Dict, List, Optional +from webviz_subsurface_components import VectorDefinition from webviz_subsurface_components.py_expression_eval import ParserError from webviz_subsurface._abbreviations.reservoir_simulation import ( @@ -13,7 +14,6 @@ get_expression_from_name, ) from webviz_subsurface.plugins._simulation_time_series.utils.from_timeseries_cumulatives import ( - create_per_interval_or_per_day_vector_description, get_cumulative_vector_name, is_per_interval_or_per_day_vector, ) @@ -25,8 +25,8 @@ def create_vector_plot_titles_from_provider_set( vector_names: List[str], expressions: List[ExpressionInfo], provider_set: ProviderSet, - resampling_frequency: Optional[Frequency], - user_defined_vector_descriptions: Dict[str, str], + user_defined_vector_definitions: Dict[str, VectorDefinition], + resampling_frequency: Optional[Frequency] = None, ) -> Dict[str, str]: """Create plot titles for vectors @@ -44,8 +44,8 @@ def create_vector_plot_titles_from_provider_set( # Provider vector if vector_name in all_vector_names: metadata = provider_set.vector_metadata(vector_name) - title = user_defined_vector_descriptions.get( - vector_name.split(":")[0], simulation_vector_description(vector_name) + title = simulation_vector_description( + vector_name, user_defined_vector_definitions ) if metadata and metadata.unit: title += f" [{simulation_unit_reformat(metadata.unit)}]" @@ -53,8 +53,8 @@ def create_vector_plot_titles_from_provider_set( # Per Interval or Per Day vector elif is_per_interval_or_per_day_vector(vector_name): - title = create_per_interval_or_per_day_vector_description( - vector_name, user_defined_vector_descriptions + title = simulation_vector_description( + vector_name, user_defined_vector_definitions ) cumulative_vector = get_cumulative_vector_name(vector_name) From 30ec4780c08ac197dc4c201c4868266bbde52b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Thu, 27 Jan 2022 15:27:00 +0100 Subject: [PATCH 06/27] Fix isort import --- .../test_utils/test_from_timeseries_cumulatives.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py index 687890751..80a06fa65 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_from_timeseries_cumulatives.py @@ -8,11 +8,11 @@ from webviz_subsurface._utils.dataframe_utils import make_date_column_datetime_object from webviz_subsurface.plugins._simulation_time_series.utils.from_timeseries_cumulatives import ( calculate_from_resampled_cumulative_vectors_df, + create_per_day_vector_name, + create_per_interval_vector_name, datetime_to_intervalstr, get_cumulative_vector_name, is_per_interval_or_per_day_vector, - create_per_day_vector_name, - create_per_interval_vector_name, ) # ******************************************************************* From 4318daae347279420e05f6beb60cdf18a5d9c8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Fri, 28 Jan 2022 08:27:09 +0100 Subject: [PATCH 07/27] Add unit test for simulation_vector_description() method Add unit test for refactored simulation_vector_description() method in reservoir_simulation.py --- .../abbreviations_tests/__init__.py | 0 .../test_reservoir_simulation.py | 94 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tests/unit_tests/abbreviations_tests/__init__.py create mode 100644 tests/unit_tests/abbreviations_tests/test_reservoir_simulation.py diff --git a/tests/unit_tests/abbreviations_tests/__init__.py b/tests/unit_tests/abbreviations_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit_tests/abbreviations_tests/test_reservoir_simulation.py b/tests/unit_tests/abbreviations_tests/test_reservoir_simulation.py new file mode 100644 index 000000000..5aa600980 --- /dev/null +++ b/tests/unit_tests/abbreviations_tests/test_reservoir_simulation.py @@ -0,0 +1,94 @@ +from webviz_subsurface_components import VectorDefinition, VectorDefinitions + +from webviz_subsurface._abbreviations.reservoir_simulation import ( + simulation_vector_description, +) + + +def test_simulation_vector_description_existing_vector() -> None: + # Verify test vector exist in VectorDefinitions + assert "WOPT" in VectorDefinitions + + # Test WITHOUT vector definitions argument + assert simulation_vector_description("WOPT:A1") == "Oil Production Total, well A1" + assert ( + simulation_vector_description("PER_DAY_WOPT:A1") + == "Average Oil Production Total Per day, well A1" + ) + assert ( + simulation_vector_description("PER_INTVL_WOPT:A1") + == "Interval Oil Production Total, well A1" + ) + assert ( + simulation_vector_description("AVG_WOPT:A1") + == "Average Oil Production Total, well A1" + ) + assert ( + simulation_vector_description("INTVL_WOPT:A1") + == "Interval Oil Production Total, well A1" + ) + + # Test WITH vector definitions argument + vector_definitions = { + "WOPT": VectorDefinition(type="well", description="Test Description"), + } + assert ( + simulation_vector_description("WOPT:A1", vector_definitions) + == "Test Description, well A1" + ) + assert ( + simulation_vector_description("PER_DAY_WOPT:A1", vector_definitions) + == "Average Test Description Per day, well A1" + ) + assert ( + simulation_vector_description("PER_INTVL_WOPT:A1", vector_definitions) + == "Interval Test Description, well A1" + ) + assert ( + simulation_vector_description("AVG_WOPT:A1", vector_definitions) + == "Average Test Description, well A1" + ) + assert ( + simulation_vector_description("INTVL_WOPT:A1", vector_definitions) + == "Interval Test Description, well A1" + ) + + +def test_simulation_vector_description_non_existing_vector() -> None: + # Verify test vector does not exist in VectorDefinitions + assert "Custom" not in VectorDefinitions + + # Test WITHOUT vector definitions argument + assert simulation_vector_description("Custom:A1") == "Custom:A1" + assert ( + simulation_vector_description("PER_DAY_Custom:A1") + == "Average Custom:A1 Per day" + ) + assert simulation_vector_description("PER_INTVL_Custom:A1") == "Interval Custom:A1" + assert simulation_vector_description("AVG_Custom:A1") == "Average Custom:A1" + assert simulation_vector_description("INTVL_Custom:A1") == "Interval Custom:A1" + + # Test WITH vector definitions argument + vector_definitions = { + "Custom": VectorDefinition(type="field", description="Custom Description"), + } + assert ( + simulation_vector_description("Custom:A1", vector_definitions) + == "Custom Description, field A1" + ) + assert ( + simulation_vector_description("PER_DAY_Custom:A1", vector_definitions) + == "Average Custom Description Per day, field A1" + ) + assert ( + simulation_vector_description("PER_INTVL_Custom:A1", vector_definitions) + == "Interval Custom Description, field A1" + ) + assert ( + simulation_vector_description("AVG_Custom:A1", vector_definitions) + == "Average Custom Description, field A1" + ) + assert ( + simulation_vector_description("INTVL_Custom:A1", vector_definitions) + == "Interval Custom Description, field A1" + ) From a0fd5378307c8d663455bdb11ef0e4ec9032a38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Fri, 28 Jan 2022 08:54:26 +0100 Subject: [PATCH 08/27] Clearyfi comment --- .../test_utils/test_provier_set_utils.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py index 065a9e50c..7a9dbf619 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_provier_set_utils.py @@ -181,28 +181,29 @@ def test_create_vector_plot_titles_from_provider_set() -> None: vector_names = ["FGIT", "WGOR:A1", "PER_DAY_WOPT:A1", "First Expression"] expressions = [FIRST_TEST_EXPRESSION] - user_defined_definitions = { - "FGIT": VectorDefinition(description="First Test title", type="field"), - "WOPT": VectorDefinition(description="Second Test title", type="well"), - } - - first_expected_titles = { + # Test WITHOUT user defined vector definitions + expected_titles = { "FGIT": "Gas Injection Total [unit_1]", "WGOR:A1": "Gas-Oil Ratio, well A1 [unit_2]", "PER_DAY_WOPT:A1": "Average Oil Production Total Per day, well A1 [unit_4/DAY]", "First Expression": "First Expression [unit_1+unit_2]", } + assert expected_titles == create_vector_plot_titles_from_provider_set( + vector_names, expressions, TEST_PROVIDER_SET, {} + ) - second_expected_titles = { + # Test WITH user defined vector definitions + user_defined_definitions = { + "FGIT": VectorDefinition(description="First Test title", type="field"), + "WOPT": VectorDefinition(description="Second Test title", type="well"), + } + expected_titles = { "FGIT": "First Test title [unit_1]", "WGOR:A1": "Gas-Oil Ratio, well A1 [unit_2]", "PER_DAY_WOPT:A1": "Average Second Test title Per day, well A1 [unit_4/DAY]", "First Expression": "First Expression [unit_1+unit_2]", } - assert first_expected_titles == create_vector_plot_titles_from_provider_set( - vector_names, expressions, TEST_PROVIDER_SET, {} - ) - assert second_expected_titles == create_vector_plot_titles_from_provider_set( + assert expected_titles == create_vector_plot_titles_from_provider_set( vector_names, expressions, TEST_PROVIDER_SET, user_defined_definitions ) From 71d1901646f5d6fb5577cb22707d8fe07e202bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Fri, 28 Jan 2022 08:56:31 +0100 Subject: [PATCH 09/27] Sort imports with isort --- webviz_subsurface/plugins/_reservoir_simulation_timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py b/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py index 43ac02040..1db3dc214 100644 --- a/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py +++ b/webviz_subsurface/plugins/_reservoir_simulation_timeseries.py @@ -59,9 +59,9 @@ expressions_from_config, get_calculated_units, get_calculated_vector_df, - get_vector_definitions_from_expressions, get_expression_from_name, get_selected_expressions, + get_vector_definitions_from_expressions, validate_predefined_expression, ) from .._utils.vector_selector import ( From 25caa4d586a972dd39d2e8e76ef4fac67db12d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Fri, 28 Jan 2022 11:57:39 +0100 Subject: [PATCH 10/27] Delete reservoir_simulation_vectors.json Adapted to utilize VectorDefinitions from webviz-subsurface-components Python-API --- .../reservoir_simulation_vectors.json | 1696 ----------------- 1 file changed, 1696 deletions(-) delete mode 100644 webviz_subsurface/_abbreviations/abbreviation_data/reservoir_simulation_vectors.json diff --git a/webviz_subsurface/_abbreviations/abbreviation_data/reservoir_simulation_vectors.json b/webviz_subsurface/_abbreviations/abbreviation_data/reservoir_simulation_vectors.json deleted file mode 100644 index 73c3ccf66..000000000 --- a/webviz_subsurface/_abbreviations/abbreviation_data/reservoir_simulation_vectors.json +++ /dev/null @@ -1,1696 +0,0 @@ -{ - "FWIP": {"type": "field", "description": "Water In Place"}, - "FGIP": {"type": "field", "description": "Gas In Place (liquid+gas phase)"}, - "FGIPG": {"type": "field", "description": "Gas In Place (gas phase)"}, - "FGIPL": {"type": "field", "description": "Gas In Place (liquid phase)"}, - "FOIP": {"type": "field", "description": "Oil In Place (liquid+gas phase)"}, - "FOIPG": {"type": "field", "description": "Oil In Place (gas phase)"}, - "FOIPL": {"type": "field", "description": "Oil In Place (liquid phase)"}, - "FOPR": {"type": "field", "description": "Oil Production Rate"}, - "FOPRA": {"type": "field", "description": "Oil Production Rate above GOC"}, - "FOPRB": {"type": "field", "description": "Oil Production Rate below GOC"}, - "FOPTA": {"type": "field", "description": "Oil Production Total above GOC"}, - "FOPTB": {"type": "field", "description": "Oil Production Total below GOC"}, - "FOPR1": {"type": "field", "description": "Oil Production Rate above GOC"}, - "FOPR2": {"type": "field", "description": "Oil Production Rate below GOC"}, - "FOPT1": {"type": "field", "description": "Oil Production Total above GOC"}, - "FOPT2": {"type": "field", "description": "Oil Production Total below GOC"}, - "FOMR": {"type": "field", "description": "Oil Mass Rate"}, - "FOMT": {"type": "field", "description": "Oil Mass Total"}, - "FODN": {"type": "field", "description": "Oil Density at Surface Conditions"}, - "FOPRH": {"type": "field", "description": "Oil Production Rate History"}, - "FOPRT": {"type": "field", "description": "Oil Production Rate Target/Limit"}, - "FOPRF": {"type": "field", "description": "Free Oil Production Rate"}, - "FOPRS": {"type": "field", "description": "Solution Oil Production Rate"}, - "FOPT": {"type": "field", "description": "Oil Production Total"}, - "FOPTH": {"type": "field", "description": "Oil Production Total History"}, - "FOPTF": {"type": "field", "description": "Free Oil Production Total"}, - "FOPTS": {"type": "field", "description": "Solution Oil Production Total"}, - "FOIR": {"type": "field", "description": "Oil Injection Rate"}, - "FOIRH": {"type": "field", "description": "Oil Injection Rate History"}, - "FOIRT": {"type": "field", "description": "Oil Injection Rate Target/Limit"}, - "FOIT": {"type": "field", "description": "Oil Injection Total"}, - "FOITH": {"type": "field", "description": "Oil Injection Total History"}, - "FOPP": {"type": "field", "description": "Oil Potential Production rate"}, - "FOPP2": {"type": "field", "description": "Oil Potential Production rate"}, - "FOPI": {"type": "field", "description": "Oil Potential Injection rate"}, - "FOPI2": {"type": "field", "description": "Oil Potential Injection rate"}, - "FOVPR": {"type": "field", "description": "Oil Voidage Production Rate"}, - "FOVPT": {"type": "field", "description": "Oil Voidage Production Total"}, - "FOVIR": {"type": "field", "description": "Oil Voidage Injection Rate"}, - "FOVIT": {"type": "field", "description": "Oil Voidage Injection Total"}, - "FOnPR": {"type": "field", "description": "nth separator stage oil rate"}, - "FOnPT": {"type": "field", "description": "nth separator stage oil total"}, - "FEOR": {"type": "field", "description": "Export Oil Rate"}, - "FEOT": {"type": "field", "description": "Export Oil Total"}, - "FEOMF": {"type": "field", "description": "Export Oil Mole Fraction"}, - "FWPR": {"type": "field", "description": "Water Production Rate"}, - "FWMR": {"type": "field", "description": "Water Mass Rate"}, - "FWMT": {"type": "field", "description": "Water Mass Total"}, - "FWPRH": {"type": "field", "description": "Water Production Rate History"}, - "FWPRT": {"type": "field", "description": "Water Production Rate Target/Limit"}, - "FWPT": {"type": "field", "description": "Water Production Total"}, - "FWPTH": {"type": "field", "description": "Water Production Total History"}, - "FWIR": {"type": "field", "description": "Water Injection Rate"}, - "FWIRH": {"type": "field", "description": "Water Injection Rate History"}, - "FWIRT": {"type": "field", "description": "Water Injection Rate Target/Limit"}, - "FWIT": {"type": "field", "description": "Water Injection Total"}, - "FWITH": {"type": "field", "description": "Water Injection Total History"}, - "FWPP": {"type": "field", "description": "Water Potential Production rate"}, - "FWPP2": {"type": "field", "description": "Water Potential Production rate"}, - "FWPI": {"type": "field", "description": "Water Potential Injection rate"}, - "FWPI2": {"type": "field", "description": "Water Potential Injection rate"}, - "FWVPR": {"type": "field", "description": "Water Voidage Production Rate"}, - "FWVPT": {"type": "field", "description": "Water Voidage Production Total"}, - "FWVIR": {"type": "field", "description": "Water Voidage Injection Rate"}, - "FWVIT": {"type": "field", "description": "Water Voidage Injection Total"}, - "FWPIR": {"type": "field", "description": "Ratio of produced water to injected water (percentage)"}, - "FWMPR": {"type": "field", "description": "Water component molar production rate"}, - "FWMPT": {"type": "field", "description": "Water component molar production total"}, - "FWMIR": {"type": "field", "description": "Water component molar injection rate"}, - "FWMIT": {"type": "field", "description": "Water component molar injection total"}, - "FGPR": {"type": "field", "description": "Gas Production Rate"}, - "FGPRA": {"type": "field", "description": "Gas Production Rate above"}, - "FGPRB": {"type": "field", "description": "Gas Production Rate below"}, - "FGPTA": {"type": "field", "description": "Gas Production Total above"}, - "FGPTB": {"type": "field", "description": "Gas Production Total below"}, - "FGPR1": {"type": "field", "description": "Gas Production Rate above GOC"}, - "FGPR2": {"type": "field", "description": "Gas Production Rate below GOC"}, - "FGPT1": {"type": "field", "description": "Gas Production Total above GOC"}, - "FGPT2": {"type": "field", "description": "Gas Production Total below GOC"}, - "FGMR": {"type": "field", "description": "Gas Mass Rate"}, - "FGMT": {"type": "field", "description": "Gas Mass Total"}, - "FGDN": {"type": "field", "description": "Gas Density at Surface Conditions"}, - "FGPRH": {"type": "field", "description": "Gas Production Rate History"}, - "FGPRT": {"type": "field", "description": "Gas Production Rate Target/Limit"}, - "FGPRF": {"type": "field", "description": "Free Gas Production Rate"}, - "FGPRS": {"type": "field", "description": "Solution Gas Production Rate"}, - "FGPT": {"type": "field", "description": "Gas Production Total"}, - "FGPTH": {"type": "field", "description": "Gas Production Total History"}, - "FGPTF": {"type": "field", "description": "Free Gas Production Total"}, - "FGPTS": {"type": "field", "description": "Solution Gas Production Total"}, - "FGIR": {"type": "field", "description": "Gas Injection Rate"}, - "FGIRH": {"type": "field", "description": "Gas Injection Rate History"}, - "FGIRT": {"type": "field", "description": "Gas Injection Rate Target/Limit"}, - "FGIT": {"type": "field", "description": "Gas Injection Total"}, - "FGITH": {"type": "field", "description": "Gas Injection Total History"}, - "FGPP": {"type": "field", "description": "Gas Potential Production rate"}, - "FGPP2": {"type": "field", "description": "Gas Potential Production rate"}, - "FGPPS": {"type": "field", "description": "Solution"}, - "FGPPS2": {"type": "field", "description": "Solution"}, - "FGPPF": {"type": "field", "description": "Free Gas Potential Production rate"}, - "FGPPF2": {"type": "field", "description": "Free Gas Potential Production rate"}, - "FGPI": {"type": "field", "description": "Gas Potential Injection rate"}, - "FGPI2": {"type": "field", "description": "Gas Potential Injection rate"}, - "FSGR": {"type": "field", "description": "Sales Gas Rate"}, - "FGSR": {"type": "field", "description": "Sales Gas Rate"}, - "FSGT": {"type": "field", "description": "Sales Gas Total"}, - "FGST": {"type": "field", "description": "Sales Gas Total"}, - "FSMF": {"type": "field", "description": "Sales Gas Mole Fraction"}, - "FFGR": {"type": "field", "description": "Fuel Gas Rate, at and below this group"}, - "FFGT": {"type": "field", "description": "Fuel Gas cumulative Total, at and below this group"}, - "FFMF": {"type": "field", "description": "Fuel Gas Mole Fraction"}, - "FGCR": {"type": "field", "description": "Gas Consumption Rate, at and below this group"}, - "FGCT": {"type": "field", "description": "Gas Consumption Total, at and below this group"}, - "FGIMR": {"type": "field", "description": "Gas Import Rate, at and below this group"}, - "FGIMT": {"type": "field", "description": "Gas Import Total, at and below this group"}, - "FGLIR": {"type": "field", "description": "Gas Lift Injection Rate"}, - "FWGPR": {"type": "field", "description": "Wet Gas Production Rate"}, - "FWGPT": {"type": "field", "description": "Wet Gas Production Total"}, - "FWGPRH": {"type": "field", "description": "Wet Gas Production Rate History"}, - "FWGIR": {"type": "field", "description": "Wet Gas Injection Rate"}, - "FWGIT": {"type": "field", "description": "Wet Gas Injection Total"}, - "FEGR": {"type": "field", "description": "Export Gas Rate"}, - "FEGT": {"type": "field", "description": "Export Gas Total"}, - "FEMF": {"type": "field", "description": "Export Gas Mole Fraction"}, - "FEXGR": {"type": "field", "description": "Excess Gas Rate"}, - "FEXGT": {"type": "field", "description": "Excess Gas Total"}, - "FRGR": {"type": "field", "description": "Re-injection Gas Rate"}, - "FRGT": {"type": "field", "description": "Re-injection Gas Total"}, - "FGnPR": {"type": "field", "description": "nth separator stage gas rate"}, - "FGnPT": {"type": "field", "description": "nth separator stage gas total"}, - "FGVPR": {"type": "field", "description": "Gas Voidage Production Rate"}, - "FGVPT": {"type": "field", "description": "Gas Voidage Production Total"}, - "FGVIR": {"type": "field", "description": "Gas Voidage Injection Rate"}, - "FGVIT": {"type": "field", "description": "Gas Voidage Injection Total"}, - "FLPR": {"type": "field", "description": "Liquid Production Rate"}, - "FLPRH": {"type": "field", "description": "Liquid Production Rate History"}, - "FLPRT": {"type": "field", "description": "Liquid Production Rate Target/Limit"}, - "FLPT": {"type": "field", "description": "Liquid Production Total"}, - "FLPTH": {"type": "field", "description": "Liquid Production Total History"}, - "FVPR": {"type": "field", "description": "Res Volume Production Rate"}, - "FVPRT": {"type": "field", "description": "Res Volume Production Rate Target/Limit"}, - "FVPT": {"type": "field", "description": "Res Volume Production Total"}, - "FVIR": {"type": "field", "description": "Res Volume Injection Rate"}, - "FVIRT": {"type": "field", "description": "Res Volume Injection Rate Target/Limit"}, - "FVIT": {"type": "field", "description": "Res Volume Injection Total"}, - "FWCT": {"type": "field", "description": "Water Cut"}, - "FWCTH": {"type": "field", "description": "Water Cut History"}, - "FGOR": {"type": "field", "description": "Gas-Oil Ratio"}, - "FGORH": {"type": "field", "description": "Gas-Oil Ratio History"}, - "FOGR": {"type": "field", "description": "Oil-Gas Ratio"}, - "FOGRH": {"type": "field", "description": "Oil-Gas Ratio History"}, - "FWGR": {"type": "field", "description": "Water-Gas Ratio"}, - "FWGRH": {"type": "field", "description": "Water-Gas Ratio History"}, - "FGLR": {"type": "field", "description": "Gas-Liquid Ratio"}, - "FGLRH": {"type": "field", "description": "Gas-Liquid Ratio History"}, - "FMCTP": {"type": "field", "description": "Mode of Control for group Production"}, - "FMCTW": {"type": "field", "description": "Mode of Control for group Water Injection"}, - "FMCTG": {"type": "field", "description": "Mode of Control for group Gas Injection"}, - "FMWPT": {"type": "field", "description": "Total number of production wells"}, - "FMWPR": {"type": "field", "description": "Number of production wells currently flowing"}, - "FMWPA": {"type": "field", "description": "Number of abandoned production wells"}, - "FMWPU": {"type": "field", "description": "Number of unused production wells"}, - "FMWPG": {"type": "field", "description": "Number of producers on group control"}, - "FMWPO": {"type": "field", "description": "Number of producers controlled by own oil rate limit"}, - "FMWPS": {"type": "field", "description": "Number of producers on own surface rate limit control"}, - "FMWPV": {"type": "field", "description": "Number of producers on own reservoir volume rate limit control"}, - "FMWPP": {"type": "field", "description": "Number of producers on pressure control"}, - "FMWPL": {"type": "field", "description": "Number of producers using artificial lift"}, - "FMWIT": {"type": "field", "description": "Total number of injection wells"}, - "FMWIN": {"type": "field", "description": "Number of injection wells currently flowing"}, - "FMWIA": {"type": "field", "description": "Number of abandoned injection wells"}, - "FMWIU": {"type": "field", "description": "Number of unused injection wells"}, - "FMWIG": {"type": "field", "description": "Number of injectors on group control"}, - "FMWIS": {"type": "field", "description": "Number of injectors on own surface rate limit control"}, - "FMWIV": {"type": "field", "description": "Number of injectors on own reservoir volume rate limit control"}, - "FMWIP": {"type": "field", "description": "Number of injectors on pressure control"}, - "FMWDR": {"type": "field", "description": "Number of drilling events this timestep"}, - "FMWDT": {"type": "field", "description": "Number of drilling events in total"}, - "FMWWO": {"type": "field", "description": "Number of workover events this timestep"}, - "FMWWT": {"type": "field", "description": "Number of workover events in total"}, - "FEPR": {"type": "field", "description": "Energy Production Rate"}, - "FEPT": {"type": "field", "description": "Energy Production Total"}, - "FNLPR": {"type": "field", "description": "NGL Production Rate"}, - "FNLPT": {"type": "field", "description": "NGL Production Total"}, - "FNLPRH": {"type": "field", "description": "NGL Production Rate History"}, - "FNLPTH": {"type": "field", "description": "NGL Production Total History"}, - "FAMF": {"type": "field", "description": "Component aqueous mole fraction, from producing completions"}, - "FXMF": {"type": "field", "description": "Liquid Mole Fraction"}, - "FYMF": {"type": "field", "description": "Vapor Mole Fraction"}, - "FXMFn": {"type": "field", "description": "Liquid Mole Fraction for nth separator stage"}, - "FYMFn": {"type": "field", "description": "Vapor Mole Fraction for nth separator stage"}, - "FZMF": {"type": "field", "description": "Total Mole Fraction"}, - "FCMPR": {"type": "field", "description": "Hydrocarbon Component Molar Production Rates"}, - "FCMPT": {"type": "field", "description": "Hydrocarbon Component"}, - "FCMIR": {"type": "field", "description": "Hydrocarbon Component Molar Injection Rates"}, - "FCMIT": {"type": "field", "description": "Hydrocarbon Component Molar Injection Totals"}, - "FHMIR": {"type": "field", "description": "Hydrocarbon Molar Injection Rate"}, - "FHMIT": {"type": "field", "description": "Hydrocarbon Molar Injection Total"}, - "FHMPR": {"type": "field", "description": "Hydrocarbon Molar Production Rate"}, - "FHMPT": {"type": "field", "description": "Hydrocarbon Molar Production Total"}, - "FCHMR": {"type": "field", "description": "Hydrocarbon Component"}, - "FCHMT": {"type": "field", "description": "Hydrocarbon Component"}, - "FCWGPR": {"type": "field", "description": "Hydrocarbon Component Wet Gas Production Rate"}, - "FCWGPT": {"type": "field", "description": "Hydrocarbon Component Wet Gas Production Total"}, - "FCWGIR": {"type": "field", "description": "Hydrocarbon Component Wet Gas Injection Rate"}, - "FCWGIT": {"type": "field", "description": "Hydrocarbon Component Wet Gas Injection Total"}, - "FCGMR": {"type": "field", "description": "Hydrocarbon component"}, - "FCGMT": {"type": "field", "description": "Hydrocarbon component"}, - "FCOMR": {"type": "field", "description": "Hydrocarbon component"}, - "FCOMT": {"type": "field", "description": "Hydrocarbon component"}, - "FCNMR": {"type": "field", "description": "Hydrocarbon component molar rates in the NGL phase"}, - "FCNWR": {"type": "field", "description": "Hydrocarbon component mass rates in the NGL phase"}, - "FCGMRn": {"type": "field", "description": "Hydrocarbon component molar rates in the gas phase for nth separator stage"}, - "FCGRn": {"type": "field", "description": "Hydrocarbon component molar rates in the gas phase for nth separator stage"}, - "FCOMRn": {"type": "field", "description": "Hydrocarbon component"}, - "FCORn": {"type": "field", "description": "Hydrocarbon component"}, - "FMUF": {"type": "field", "description": "Make-up fraction"}, - "FAMR": {"type": "field", "description": "Make-up gas rate"}, - "FAMT": {"type": "field", "description": "Make-up gas total"}, - "FGSPR": {"type": "field", "description": "Target sustainable rate for most recent sustainable capacity test for gas"}, - "FGSRL": {"type": "field", "description": "Maximum tested rate sustained for the test period during the most recent sustainable capacity test for gas"}, - "FGSRU": {"type": "field", "description": "Minimum tested rate not sustained for the test period during the most recent sustainable capacity test for gas"}, - "FGSSP": {"type": "field", "description": "Period for which target sustainable rate could be maintained for the most recent sustainable capacity test for gas"}, - "FGSTP": {"type": "field", "description": "Test period for the most recent sustainable capacity test for gas"}, - "FOSPR": {"type": "field", "description": "Target sustainable rate for most recent sustainable capacity test for oil"}, - "FOSRL": {"type": "field", "description": "Maximum tested rate sustained for the test period during the most recent sustainable capacity test for oil"}, - "FOSRU": {"type": "field", "description": "Minimum tested rate not sustained for the test period during the most recent sustainable capacity test for oil"}, - "FOSSP": {"type": "field", "description": "Period for which target sustainable rate could be maintained for the most recent sustainable capacity test for oil"}, - "FOSTP": {"type": "field", "description": "Test period for the most recent sustainable capacity test for oil"}, - "FWSPR": {"type": "field", "description": "Target sustainable rate for most recent sustainable capacity test for water"}, - "FWSRL": {"type": "field", "description": "Maximum tested rate sustained for the test period during the most recent sustainable capacity test for water"}, - "FWSRU": {"type": "field", "description": "Minimum tested rate not sustained for the test period during the most recent sustainable capacity test for water"}, - "FWSSP": {"type": "field", "description": "Period for which target sustainable rate could be maintained for the most recent sustainable capacity test for water"}, - "FWSTP": {"type": "field", "description": "Test period for the most recent sustainable capacity test for water"}, - "FGPRG": {"type": "field", "description": "Gas production rate"}, - "FOPRG": {"type": "field", "description": "Oil production rate"}, - "FNLPRG": {"type": "field", "description": "NGL production rate"}, - "FXMFG": {"type": "field", "description": "Liquid mole fraction"}, - "FYMFG": {"type": "field", "description": "Vapor mole fraction"}, - "FCOMRG": {"type": "field", "description": "Hydrocarbon component"}, - "FCGMRG": {"type": "field", "description": "Hydrocarbon component molar rates in the gas phase"}, - "FCNMRG": {"type": "field", "description": "Hydrocarbon component molar rates in the NGL phase"}, - "FPR": {"type": "field", "description": "Pressure average value"}, - "FPRH": {"type": "field", "description": "Pressure average value"}, - "FPRP": {"type": "field", "description": "Pressure average value"}, - "FPRGZ": {"type": "field", "description": "P/Z"}, - "FRS": {"type": "field", "description": "Gas-oil ratio"}, - "FRV": {"type": "field", "description": "Oil-gas ratio"}, - "FCHIP": {"type": "field", "description": "Component Hydrocarbon as Wet Gas"}, - "FCMIP": {"type": "field", "description": "Component Hydrocarbon as Moles"}, - "FPPC": {"type": "field", "description": "Initial Contact Corrected Potential"}, - "FREAC": {"type": "field", "description": "Reaction rate. The reaction number is given as a component index"}, - "FREAT": {"type": "field", "description": "Reaction total. The reaction number is given as a component index"}, - "FRPV": {"type": "field", "description": "Pore Volume at Reservoir conditions"}, - "FOPV": {"type": "field", "description": "Pore Volume containing Oil"}, - "FWPV": {"type": "field", "description": "Pore Volume containing Water"}, - "FGPV": {"type": "field", "description": "Pore Volume containing Gas"}, - "FHPV": {"type": "field", "description": "Pore Volume containing Hydrocarbon"}, - "FRTM": {"type": "field", "description": "Transmissibility Multiplier associated with rock compaction"}, - "FOE": {"type": "field", "description": "(OIP(initial) - OIP(now)) / OIP(initial)"}, - "FOEW": {"type": "field", "description": "Oil Production from Wells / OIP(initial)"}, - "FOEIW": {"type": "field", "description": "(OIP(initial) - OIP(now)) / Initial Mobile Oil with respect to Water"}, - "FOEWW": {"type": "field", "description": "Oil Production from Wells / Initial Mobile Oil with respect to Water"}, - "FOEIG": {"type": "field", "description": "(OIP(initial) - OIP(now)) / Initial Mobile Oil with respect to Gas"}, - "FOEWG": {"type": "field", "description": "Oil Production from Wells / Initial Mobile Oil with respect to Gas"}, - "FORMR": {"type": "field", "description": "Total stock tank oil produced by rock compaction"}, - "FORMW": {"type": "field", "description": "Total stock tank oil produced by water influx"}, - "FORMG": {"type": "field", "description": "Total stock tank oil produced by gas influx"}, - "FORME": {"type": "field", "description": "Total stock tank oil produced by oil expansion"}, - "FORMS": {"type": "field", "description": "Total stock tank oil produced by solution gas"}, - "FORMF": {"type": "field", "description": "Total stock tank oil produced by free gas influx"}, - "FORMX": {"type": "field", "description": "Total stock tank oil produced by 'traced' water influx"}, - "FORMY": {"type": "field", "description": "Total stock tank oil produced by other water influx"}, - "FORFR": {"type": "field", "description": "Fraction of total oil produced by rock compaction"}, - "FORFW": {"type": "field", "description": "Fraction of total oil produced by water influx"}, - "FORFG": {"type": "field", "description": "Fraction of total oil produced by gas influx"}, - "FORFE": {"type": "field", "description": "Fraction of total oil produced by oil expansion"}, - "FORFS": {"type": "field", "description": "Fraction of total oil produced by solution gas"}, - "FORFF": {"type": "field", "description": "Fraction of total oil produced by free gas influx"}, - "FORFX": {"type": "field", "description": "Fraction of total oil produced by 'traced' water influx"}, - "FORFY": {"type": "field", "description": "Fraction of total oil produced by other water influx"}, - "FAQR": {"type": "field", "description": "Aquifer influx rate"}, - "FAQT": {"type": "field", "description": "Cumulative aquifer influx"}, - "FAQRG": {"type": "field", "description": "Aquifer influx rate"}, - "FAQTG": {"type": "field", "description": "Cumulative aquifer influx"}, - "FAQER": {"type": "field", "description": "Aquifer thermal energy influx rate"}, - "FAQET": {"type": "field", "description": "Cumulative aquifer thermal energy influx"}, - "FNQR": {"type": "field", "description": "Aquifer influx rate"}, - "FNQT": {"type": "field", "description": "Cumulative aquifer influx"}, - "FTMR": {"type": "field", "description": "Traced mass Rate"}, - "FTMT": {"type": "field", "description": "Traced mass Total"}, - "FTQR": {"type": "field", "description": "Traced molar Rate"}, - "FTCM": {"type": "field", "description": "Tracer Carrier molar Rate"}, - "FTMF": {"type": "field", "description": "Traced molar fraction"}, - "FTVL": {"type": "field", "description": "Traced liquid volume rate"}, - "FTVV": {"type": "field", "description": "Traced vapor volume rate"}, - "FTTL": {"type": "field", "description": "Traced liquid volume total"}, - "FTTV": {"type": "field", "description": "Traced vapor volume total"}, - "FTML": {"type": "field", "description": "Traced mass liquid rate"}, - "FTMV": {"type": "field", "description": "Traced mass vapor rate"}, - "FTLM": {"type": "field", "description": "Traced mass liquid total"}, - "FTVM": {"type": "field", "description": "Traced mass vapor total"}, - "FAPI": {"type": "field", "description": "Oil API"}, - "FSPR": {"type": "field", "description": "Salt Production Rate"}, - "FSPT": {"type": "field", "description": "Salt Production Total"}, - "FSIR": {"type": "field", "description": "Salt Injection Rate"}, - "FSIT": {"type": "field", "description": "Salt Injection Total"}, - "FSPC": {"type": "field", "description": "Salt Production Concentration"}, - "FSIC": {"type": "field", "description": "Salt Injection Concentration"}, - "FSIP": {"type": "field", "description": "Salt In Place"}, - "GTPRANI": {"type": "field", "description": "Anion Production Rate"}, - "GTPTANI": {"type": "field", "description": "Anion Production Total"}, - "GTIRANI": {"type": "field", "description": "Anion Injection Rate"}, - "GTITANI": {"type": "field", "description": "Anion Injection Total"}, - "GTPRCAT": {"type": "field", "description": "Cation Production Rate"}, - "GTPTCAT": {"type": "field", "description": "Cation Production Total"}, - "GTIRCAT": {"type": "field", "description": "Cation Injection Rate"}, - "GTITCAT": {"type": "field", "description": "Cation Injection Total"}, - "FTPCHEA": {"type": "field", "description": "Production Temperature"}, - "FTICHEA": {"type": "field", "description": "Injection Temperature"}, - "FTPRHEA": {"type": "field", "description": "Energy flows"}, - "FTPTHEA": {"type": "field", "description": "Energy Production Total"}, - "FTIRHEA": {"type": "field", "description": "Energy flows"}, - "FTITHEA": {"type": "field", "description": "Energy Injection Total"}, - "FTIPTHEA": {"type": "field", "description": "Difference in Energy in place between current and initial time"}, - "FTPR": {"type": "field", "description": "Tracer Production Rate"}, - "FTPT": {"type": "field", "description": "Tracer Production Total"}, - "FTPC": {"type": "field", "description": "Tracer Production Concentration"}, - "FTIR": {"type": "field", "description": "Tracer Injection Rate"}, - "FTIT": {"type": "field", "description": "Tracer Injection Total"}, - "FTIC": {"type": "field", "description": "Tracer Injection Concentration"}, - "FTIPT": {"type": "field", "description": "Tracer In Place"}, - "FTIPF": {"type": "field", "description": "Tracer In Place"}, - "FTIPS": {"type": "field", "description": "Tracer In Place"}, - "FTIP#": {"type": "field", "description": " Tracer In Place in phase # (1,2,3,...)"}, - "FTADS": {"type": "field", "description": "Tracer Adsorption total"}, - "FTDCY": {"type": "field", "description": "Decayed tracer"}, - "FTIRF": {"type": "field", "description": "Tracer Injection Rate"}, - "FTIRS": {"type": "field", "description": "Tracer Injection Rate"}, - "FTPRF": {"type": "field", "description": "Tracer Production Rate"}, - "FTPRS": {"type": "field", "description": "Tracer Production Rate"}, - "FTITF": {"type": "field", "description": "Tracer Injection Total"}, - "FTITS": {"type": "field", "description": "Tracer Injection Total"}, - "FTPTF": {"type": "field", "description": "Tracer Production Total"}, - "FTPTS": {"type": "field", "description": "Tracer Production Total"}, - "FTICF": {"type": "field", "description": "Tracer Injection Concentration"}, - "FTICS": {"type": "field", "description": "Tracer Injection Concentration"}, - "FTPCF": {"type": "field", "description": "Tracer Production"}, - "FTPCS": {"type": "field", "description": "Tracer Production"}, - "FMPR": {"type": "field", "description": "Methane Production Rate"}, - "FMPT": {"type": "field", "description": "Methane Production Total"}, - "FMIR": {"type": "field", "description": "Methane Injection Rate"}, - "FMIT": {"type": "field", "description": "Methane Injection Total"}, - "FCGC": {"type": "field", "description": "Bulk Coal Gas Concentration"}, - "FCSC": {"type": "field", "description": "Bulk Coal Solvent Concentration"}, - "FTPRFOA": {"type": "field", "description": "Production Rate"}, - "FTPTFOA": {"type": "field", "description": "Production Total"}, - "FTIRFOA": {"type": "field", "description": "Injection Rate"}, - "FTITFOA": {"type": "field", "description": "Injection Total"}, - "FTIPTFOA": {"type": "field", "description": "In Solution"}, - "FTADSFOA": {"type": "field", "description": "Adsorption total"}, - "FTDCYFOA": {"type": "field", "description": "Decayed tracer"}, - "FTMOBFOA": {"type": "field", "description": "Gas mobility factor"}, - "FGDC": {"type": "field", "description": "Gas Delivery Capacity"}, - "FGDCQ": {"type": "field", "description": "Field/Group Gas DCQ"}, - "FGCV": {"type": "field", "description": "Gas Calorific Value"}, - "FGQ": {"type": "field", "description": "Gas molar Quality"}, - "FESR": {"type": "field", "description": "Energy Sales Rate"}, - "FEST": {"type": "field", "description": "Energy Sales Total"}, - "FEDC": {"type": "field", "description": "Energy Delivery Capacity"}, - "FEDCQ": {"type": "field", "description": "Energy DCQ"}, - "FCPR": {"type": "field", "description": "Polymer Production Rate"}, - "FCPC": {"type": "field", "description": "Polymer Production Concentration"}, - "FCPT": {"type": "field", "description": "Polymer Production Total"}, - "FCIR": {"type": "field", "description": "Polymer Injection Rate"}, - "FCIC": {"type": "field", "description": "Polymer Injection Concentration"}, - "FCIT": {"type": "field", "description": "Polymer Injection Total"}, - "FCIP": {"type": "field", "description": "Polymer In Solution"}, - "FCAD": {"type": "field", "description": "Polymer Adsorption total"}, - "PSSPR": {"type": "field", "description": "Log of the pressure change per unit time"}, - "PSSSO": {"type": "field", "description": "Log of the oil saturation change per unit time"}, - "PSSSW": {"type": "field", "description": "Log of the water saturation change per unit time"}, - "PSSSG": {"type": "field", "description": "Log of the gas saturation change per unit time"}, - "PSSSC": {"type": "field", "description": "Log of the salt concentration change per unit time"}, - "FNPR": {"type": "field", "description": "Solvent Production Rate"}, - "FNPT": {"type": "field", "description": "Solvent Production Total"}, - "FNIR": {"type": "field", "description": "Solvent Injection Rate"}, - "FNIT": {"type": "field", "description": "Solvent Injection Total"}, - "FNIP": {"type": "field", "description": "Solvent In Place"}, - "FTPRSUR": {"type": "field", "description": "Production Rate"}, - "FTPTSUR": {"type": "field", "description": "Production Total"}, - "FTIRSUR": {"type": "field", "description": "Injection Rate"}, - "FTITSUR": {"type": "field", "description": "Injection Total"}, - "FTIPTSUR": {"type": "field", "description": "In Solution"}, - "FTADSUR": {"type": "field", "description": "Adsorption total"}, - "FTPRALK": {"type": "field", "description": "Production Rate"}, - "FTPTALK": {"type": "field", "description": "Production Total"}, - "FTIRALK": {"type": "field", "description": "Injection Rate"}, - "FTITALK": {"type": "field", "description": "Injection Total"}, - "FU": {"type": "field", "description": "User-defined field quantity"}, - "GOPR": {"type": "group", "description": "Oil Production Rate"}, - "GOPRA": {"type": "group", "description": "Oil Production Rate above GOC"}, - "GOPRB": {"type": "group", "description": "Oil Production Rate below GOC"}, - "GOPTA": {"type": "group", "description": "Oil Production Total above GOC"}, - "GOPTB": {"type": "group", "description": "Oil Production Total below GOC"}, - "GOPR1": {"type": "group", "description": "Oil Production Rate above GOC"}, - "GOPR2": {"type": "group", "description": "Oil Production Rate below GOC"}, - "GOPT1": {"type": "group", "description": "Oil Production Total above GOC"}, - "GOPT2": {"type": "group", "description": "Oil Production Total below GOC"}, - "GOMR": {"type": "group", "description": "Oil Mass Rate"}, - "GOMT": {"type": "group", "description": "Oil Mass Total"}, - "GODN": {"type": "group", "description": "Oil Density at Surface Conditions"}, - "GOPRH": {"type": "group", "description": "Oil Production Rate History"}, - "GOPRT": {"type": "group", "description": "Oil Production Rate Target/Limit"}, - "GOPRL": {"type": "group", "description": "Oil Production Rate Target/Limit"}, - "GOPRF": {"type": "group", "description": "Free Oil Production Rate"}, - "GOPRS": {"type": "group", "description": "Solution Oil Production Rate"}, - "GOPT": {"type": "group", "description": "Oil Production Total"}, - "GOPTH": {"type": "group", "description": "Oil Production Total History"}, - "GOPTF": {"type": "group", "description": "Free Oil Production Total"}, - "GOPTS": {"type": "group", "description": "Solution Oil Production Total"}, - "GOIR": {"type": "group", "description": "Oil Injection Rate"}, - "GOIRH": {"type": "group", "description": "Oil Injection Rate History"}, - "GOIRT": {"type": "group", "description": "Oil Injection Rate Target/Limit"}, - "GOIRL": {"type": "group", "description": "Oil Injection Rate Target/Limit"}, - "GOIT": {"type": "group", "description": "Oil Injection Total"}, - "GOITH": {"type": "group", "description": "Oil Injection Total History"}, - "GOPP": {"type": "group", "description": "Oil Potential Production rate"}, - "GOPP2": {"type": "group", "description": "Oil Potential Production rate"}, - "GOPI": {"type": "group", "description": "Oil Potential Injection rate"}, - "GOPI2": {"type": "group", "description": "Oil Potential Injection rate"}, - "GOPGR": {"type": "group", "description": "Oil Production Guide Rate"}, - "GOIGR": {"type": "group", "description": "Oil Injection Guide Rate"}, - "GOVPR": {"type": "group", "description": "Oil Voidage Production Rate"}, - "GOVPT": {"type": "group", "description": "Oil Voidage Production Total"}, - "GOVIR": {"type": "group", "description": "Oil Voidage Injection Rate"}, - "GOVIT": {"type": "group", "description": "Oil Voidage Injection Total"}, - "GOnPR": {"type": "group", "description": "nth separator stage oil rate"}, - "GOnPT": {"type": "group", "description": "nth separator stage oil total"}, - "GEOR": {"type": "group", "description": "Export Oil Rate"}, - "GEOT": {"type": "group", "description": "Export Oil Total"}, - "GEOMF": {"type": "group", "description": "Export Oil Mole Fraction"}, - "GWPR": {"type": "group", "description": "Water Production Rate"}, - "GWMR": {"type": "group", "description": "Water Mass Rate"}, - "GWMT": {"type": "group", "description": "Water Mass Total"}, - "GWPRH": {"type": "group", "description": "Water Production Rate History"}, - "GWPRT": {"type": "group", "description": "Water Production Rate Target/Limit"}, - "GWPRL": {"type": "group", "description": "Water Production Rate Target/Limit"}, - "GWPT": {"type": "group", "description": "Water Production Total"}, - "GWPTH": {"type": "group", "description": "Water Production Total History"}, - "GWIR": {"type": "group", "description": "Water Injection Rate"}, - "GWIRH": {"type": "group", "description": "Water Injection Rate History"}, - "GWIRT": {"type": "group", "description": "Water Injection Rate Target/Limit"}, - "GWIRL": {"type": "group", "description": "Water Injection Rate Target/Limit"}, - "GWIT": {"type": "group", "description": "Water Injection Total"}, - "GWITH": {"type": "group", "description": "Water Injection Total History"}, - "GWPP": {"type": "group", "description": "Water Potential Production rate"}, - "GWPP2": {"type": "group", "description": "Water Potential Production rate"}, - "GWPI": {"type": "group", "description": "Water Potential Injection rate"}, - "GWPI2": {"type": "group", "description": "Water Potential Injection rate"}, - "GWPGR": {"type": "group", "description": "Water Production Guide Rate"}, - "GWIGR": {"type": "group", "description": "Water Injection Guide Rate"}, - "GWVPR": {"type": "group", "description": "Water Voidage Production Rate"}, - "GWVPT": {"type": "group", "description": "Water Voidage Production Total"}, - "GWVIR": {"type": "group", "description": "Water Voidage Injection Rate"}, - "GWVIT": {"type": "group", "description": "Water Voidage Injection Total"}, - "GWPIR": {"type": "group", "description": "Ratio of produced water to injected water (percentage)"}, - "GWMPR": {"type": "group", "description": "Water component molar production rate"}, - "GWMPT": {"type": "group", "description": "Water component molar production total"}, - "GWMIR": {"type": "group", "description": "Water component molar injection rate"}, - "GWMIT": {"type": "group", "description": "Water component molar injection total"}, - "GGPR": {"type": "group", "description": "Gas Production Rate"}, - "GGPRA": {"type": "group", "description": "Gas Production Rate above"}, - "GGPRB": {"type": "group", "description": "Gas Production Rate below"}, - "GGPTA": {"type": "group", "description": "Gas Production Total above"}, - "GGPTB": {"type": "group", "description": "Gas Production Total below"}, - "GGPR1": {"type": "group", "description": "Gas Production Rate above GOC"}, - "GGPR2": {"type": "group", "description": "Gas Production Rate below GOC"}, - "GGPT1": {"type": "group", "description": "Gas Production Total above GOC"}, - "GGPT2": {"type": "group", "description": "Gas Production Total below GOC"}, - "GGMR": {"type": "group", "description": "Gas Mass Rate"}, - "GGMT": {"type": "group", "description": "Gas Mass Total"}, - "GGDN": {"type": "group", "description": "Gas Density at Surface Conditions"}, - "GGPRH": {"type": "group", "description": "Gas Production Rate History"}, - "GGPRT": {"type": "group", "description": "Gas Production Rate Target/Limit"}, - "GGPRL": {"type": "group", "description": "Gas Production Rate Target/Limit"}, - "GGPRF": {"type": "group", "description": "Free Gas Production Rate"}, - "GGPRS": {"type": "group", "description": "Solution Gas Production Rate"}, - "GGPT": {"type": "group", "description": "Gas Production Total"}, - "GGPTH": {"type": "group", "description": "Gas Production Total History"}, - "GGPTF": {"type": "group", "description": "Free Gas Production Total"}, - "GGPTS": {"type": "group", "description": "Solution Gas Production Total"}, - "GGIR": {"type": "group", "description": "Gas Injection Rate"}, - "GGIRH": {"type": "group", "description": "Gas Injection Rate History"}, - "GGIRT": {"type": "group", "description": "Gas Injection Rate Target/Limit"}, - "GGIRL": {"type": "group", "description": "Gas Injection Rate Target/Limit"}, - "GGIT": {"type": "group", "description": "Gas Injection Total"}, - "GGITH": {"type": "group", "description": "Gas Injection Total History"}, - "GGPP": {"type": "group", "description": "Gas Potential Production rate"}, - "GGPP2": {"type": "group", "description": "Gas Potential Production rate"}, - "GGPPS": {"type": "group", "description": "Solution"}, - "GGPPS2": {"type": "group", "description": "Solution"}, - "GGPPF": {"type": "group", "description": "Free Gas Potential Production rate"}, - "GGPPF2": {"type": "group", "description": "Free Gas Potential Production rate"}, - "GGPI": {"type": "group", "description": "Gas Potential Injection rate"}, - "GGPI2": {"type": "group", "description": "Gas Potential Injection rate"}, - "GGPGR": {"type": "group", "description": "Gas Production Guide Rate"}, - "GGIGR": {"type": "group", "description": "Gas Injection Guide Rate"}, - "GSGR": {"type": "group", "description": "Sales Gas Rate"}, - "GGSR": {"type": "group", "description": "Sales Gas Rate"}, - "GSGT": {"type": "group", "description": "Sales Gas Total"}, - "GGST": {"type": "group", "description": "Sales Gas Total"}, - "GSMF": {"type": "group", "description": "Sales Gas Mole Fraction"}, - "GFGR": {"type": "group", "description": "Fuel Gas Rate, at and below this group"}, - "GFGT": {"type": "group", "description": "Fuel Gas cumulative Total, at and below this group"}, - "GFMF": {"type": "group", "description": "Fuel Gas Mole Fraction"}, - "GGCR": {"type": "group", "description": "Gas Consumption Rate, at and below this group"}, - "GGCT": {"type": "group", "description": "Gas Consumption Total, at and below this group"}, - "GGIMR": {"type": "group", "description": "Gas Import Rate, at and below this group"}, - "GGIMT": {"type": "group", "description": "Gas Import Total, at and below this group"}, - "GGLIR": {"type": "group", "description": "Gas Lift Injection Rate"}, - "GWGPR": {"type": "group", "description": "Wet Gas Production Rate"}, - "GWGPT": {"type": "group", "description": "Wet Gas Production Total"}, - "GWGPRH": {"type": "group", "description": "Wet Gas Production Rate History"}, - "GWGIR": {"type": "group", "description": "Wet Gas Injection Rate"}, - "GWGIT": {"type": "group", "description": "Wet Gas Injection Total"}, - "GEGR": {"type": "group", "description": "Export Gas Rate"}, - "GEGT": {"type": "group", "description": "Export Gas Total"}, - "GEMF": {"type": "group", "description": "Export Gas Mole Fraction"}, - "GEXGR": {"type": "group", "description": "Excess Gas Rate"}, - "GEXGT": {"type": "group", "description": "Excess Gas Total"}, - "GRGR": {"type": "group", "description": "Re-injection Gas Rate"}, - "GRGT": {"type": "group", "description": "Re-injection Gas Total"}, - "GGnPR": {"type": "group", "description": "nth separator stage gas rate"}, - "GGnPT": {"type": "group", "description": "nth separator stage gas total"}, - "GGVPR": {"type": "group", "description": "Gas Voidage Production Rate"}, - "GGVPT": {"type": "group", "description": "Gas Voidage Production Total"}, - "GGVIR": {"type": "group", "description": "Gas Voidage Injection Rate"}, - "GGVIT": {"type": "group", "description": "Gas Voidage Injection Total"}, - "GGQ": {"type": "group", "description": "Gas Quality"}, - "GLPR": {"type": "group", "description": "Liquid Production Rate"}, - "GLPRH": {"type": "group", "description": "Liquid Production Rate History"}, - "GLPRT": {"type": "group", "description": "Liquid Production Rate Target/Limit"}, - "GLPRL": {"type": "group", "description": "Liquid Production Rate Target/Limit"}, - "GLPT": {"type": "group", "description": "Liquid Production Total"}, - "GLPTH": {"type": "group", "description": "Liquid Production Total History"}, - "GVPR": {"type": "group", "description": "Res Volume Production Rate"}, - "GVPRT": {"type": "group", "description": "Res Volume Production Rate Target/Limit"}, - "GVPRL": {"type": "group", "description": "Res Volume Production Rate Target/Limit"}, - "GVPT": {"type": "group", "description": "Res Volume Production Total"}, - "GVPGR": {"type": "group", "description": "Res Volume Production Guide Rate"}, - "GVIR": {"type": "group", "description": "Res Volume Injection Rate"}, - "GVIRT": {"type": "group", "description": "Res Volume Injection Rate Target/Limit"}, - "GVIRL": {"type": "group", "description": "Res Volume Injection Rate Target/Limit"}, - "GVIT": {"type": "group", "description": "Res Volume Injection Total"}, - "GWCT": {"type": "group", "description": "Water Cut"}, - "GWCTH": {"type": "group", "description": "Water Cut History"}, - "GGOR": {"type": "group", "description": "Gas-Oil Ratio"}, - "GGORH": {"type": "group", "description": "Gas-Oil Ratio History"}, - "GOGR": {"type": "group", "description": "Oil-Gas Ratio"}, - "GOGRH": {"type": "group", "description": "Oil-Gas Ratio History"}, - "GWGR": {"type": "group", "description": "Water-Gas Ratio"}, - "GWGRH": {"type": "group", "description": "Water-Gas Ratio History"}, - "GGLR": {"type": "group", "description": "Gas-Liquid Ratio"}, - "GGLRH": {"type": "group", "description": "Gas-Liquid Ratio History"}, - "GMCTP": {"type": "group", "description": "Mode of Control for group Production"}, - "GMCTW": {"type": "group", "description": "Mode of Control for group Water Injection"}, - "GMCTG": {"type": "group", "description": "Mode of Control for group Gas Injection"}, - "GMWPT": {"type": "group", "description": "Total number of production wells"}, - "GMWPR": {"type": "group", "description": "Number of production wells currently flowing"}, - "GMWPA": {"type": "group", "description": "Number of abandoned production wells"}, - "GMWPU": {"type": "group", "description": "Number of unused production wells"}, - "GMWPG": {"type": "group", "description": "Number of producers on group control"}, - "GMWPO": {"type": "group", "description": "Number of producers controlled by own oil rate limit"}, - "GMWPS": {"type": "group", "description": "Number of producers on own surface rate limit control"}, - "GMWPV": {"type": "group", "description": "Number of producers on own reservoir volume rate limit control"}, - "GMWPP": {"type": "group", "description": "Number of producers on pressure control"}, - "GMWPL": {"type": "group", "description": "Number of producers using artificial lift"}, - "GMWIT": {"type": "group", "description": "Total number of injection wells"}, - "GMWIN": {"type": "group", "description": "Number of injection wells currently flowing"}, - "GMWIA": {"type": "group", "description": "Number of abandoned injection wells"}, - "GMWIU": {"type": "group", "description": "Number of unused injection wells"}, - "GMWIG": {"type": "group", "description": "Number of injectors on group control"}, - "GMWIS": {"type": "group", "description": "Number of injectors on own surface rate limit control"}, - "GMWIV": {"type": "group", "description": "Number of injectors on own reservoir volume rate limit control"}, - "GMWIP": {"type": "group", "description": "Number of injectors on pressure control"}, - "GMWDR": {"type": "group", "description": "Number of drilling events this timestep"}, - "GMWDT": {"type": "group", "description": "Number of drilling events in total"}, - "GMWWO": {"type": "group", "description": "Number of workover events this timestep"}, - "GMWWT": {"type": "group", "description": "Number of workover events in total"}, - "GEPR": {"type": "group", "description": "Energy Production Rate"}, - "GEPT": {"type": "group", "description": "Energy Production Total"}, - "GEFF": {"type": "group", "description": "Efficiency Factor"}, - "GNLPR": {"type": "group", "description": "NGL Production Rate"}, - "GNLPT": {"type": "group", "description": "NGL Production Total"}, - "GNLPRH": {"type": "group", "description": "NGL Production Rate History"}, - "GNLPTH": {"type": "group", "description": "NGL Production Total History"}, - "GAMF": {"type": "group", "description": "Component aqueous mole fraction, from producing completions"}, - "GXMF": {"type": "group", "description": "Liquid Mole Fraction"}, - "GYMF": {"type": "group", "description": "Vapor Mole Fraction"}, - "GXMFn": {"type": "group", "description": "Liquid Mole Fraction for nth separator stage"}, - "GYMFn": {"type": "group", "description": "Vapor Mole Fraction for nth separator stage"}, - "GZMF": {"type": "group", "description": "Total Mole Fraction"}, - "GCMPR": {"type": "group", "description": "Hydrocarbon Component Molar Production Rates"}, - "GCMPT": {"type": "group", "description": "Hydrocarbon Component"}, - "GCMIR": {"type": "group", "description": "Hydrocarbon Component Molar Injection Rates"}, - "GCMIT": {"type": "group", "description": "Hydrocarbon Component Molar Injection Totals"}, - "GHMIR": {"type": "group", "description": "Hydrocarbon Molar Injection Rate"}, - "GHMIT": {"type": "group", "description": "Hydrocarbon Molar Injection Total"}, - "GHMPR": {"type": "group", "description": "Hydrocarbon Molar Production Rate"}, - "GHMPT": {"type": "group", "description": "Hydrocarbon Molar Production Total"}, - "GCHMR": {"type": "group", "description": "Hydrocarbon Component"}, - "GCHMT": {"type": "group", "description": "Hydrocarbon Component"}, - "GCWGPR": {"type": "group", "description": "Hydrocarbon Component Wet Gas Production Rate"}, - "GCWGPT": {"type": "group", "description": "Hydrocarbon Component Wet Gas Production Total"}, - "GCWGIR": {"type": "group", "description": "Hydrocarbon Component Wet Gas Injection Rate"}, - "GCWGIT": {"type": "group", "description": "Hydrocarbon Component Wet Gas Injection Total"}, - "GCGMR": {"type": "group", "description": "Hydrocarbon component"}, - "GCGMT": {"type": "group", "description": "Hydrocarbon component"}, - "GCOMR": {"type": "group", "description": "Hydrocarbon component"}, - "GCOMT": {"type": "group", "description": "Hydrocarbon component"}, - "GCNMR": {"type": "group", "description": "Hydrocarbon component molar rates in the NGL phase"}, - "GCNWR": {"type": "group", "description": "Hydrocarbon component mass rates in the NGL phase"}, - "GCGMRn": {"type": "group", "description": "Hydrocarbon component molar rates in the gas phase for nth separator stage"}, - "GCGRn": {"type": "group", "description": "Hydrocarbon component molar rates in the gas phase for nth separator stage"}, - "GCOMRn": {"type": "group", "description": "Hydrocarbon component"}, - "GCORn": {"type": "group", "description": "Hydrocarbon component"}, - "GMUF": {"type": "group", "description": "Make-up fraction"}, - "GAMR": {"type": "group", "description": "Make-up gas rate"}, - "GAMT": {"type": "group", "description": "Make-up gas total"}, - "GGSPR": {"type": "group", "description": "Target sustainable rate for most recent sustainable capacity test for gas"}, - "GGSRL": {"type": "group", "description": "Maximum tested rate sustained for the test period during the most recent sustainable capacity test for gas"}, - "GGSRU": {"type": "group", "description": "Minimum tested rate not sustained for the test period during the most recent sustainable capacity test for gas"}, - "GGSSP": {"type": "group", "description": "Period for which target sustainable rate could be maintained for the most recent sustainable capacity test for gas"}, - "GGSTP": {"type": "group", "description": "Test period for the most recent sustainable capacity test for gas"}, - "GOSPR": {"type": "group", "description": "Target sustainable rate for most recent sustainable capacity test for oil"}, - "GOSRL": {"type": "group", "description": "Maximum tested rate sustained for the test period during the most recent sustainable capacity test for oil"}, - "GOSRU": {"type": "group", "description": "Minimum tested rate not sustained for the test period during the most recent sustainable capacity test for oil"}, - "GOSSP": {"type": "group", "description": "Period for which target sustainable rate could be maintained for the most recent sustainable capacity test for oil"}, - "GOSTP": {"type": "group", "description": "Test period for the most recent sustainable capacity test for oil"}, - "GWSPR": {"type": "group", "description": "Target sustainable rate for most recent sustainable capacity test for water"}, - "GWSRL": {"type": "group", "description": "Maximum tested rate sustained for the test period during the most recent sustainable capacity test for water"}, - "GWSRU": {"type": "group", "description": "Minimum tested rate not sustained for the test period during the most recent sustainable capacity test for water"}, - "GWSSP": {"type": "group", "description": "Period for which target sustainable rate could be maintained for the most recent sustainable capacity test for water"}, - "GWSTP": {"type": "group", "description": "Test period for the most recent sustainable capacity test for water"}, - "GGPRG": {"type": "group", "description": "Gas production rate"}, - "GOPRG": {"type": "group", "description": "Oil production rate"}, - "GNLPRG": {"type": "group", "description": "NGL production rate"}, - "GXMFG": {"type": "group", "description": "Liquid mole fraction"}, - "GYMFG": {"type": "group", "description": "Vapor mole fraction"}, - "GCOMRG": {"type": "group", "description": "Hydrocarbon component"}, - "GCGMRG": {"type": "group", "description": "Hydrocarbon component molar rates in the gas phase"}, - "GCNMRG": {"type": "group", "description": "Hydrocarbon component molar rates in the NGL phase"}, - "GTPR": {"type": "group", "description": "Tracer Production Rate"}, - "GTPT": {"type": "group", "description": "Tracer Production Total"}, - "GTPC": {"type": "group", "description": "Tracer Production Concentration"}, - "GTIR": {"type": "group", "description": "Tracer Injection Rate"}, - "GTIT": {"type": "group", "description": "Tracer Injection Total"}, - "GTIC": {"type": "group", "description": "Tracer Injection Concentration"}, - "GTMR": {"type": "group", "description": "Traced mass Rate"}, - "GTMT": {"type": "group", "description": "Traced mass Total"}, - "GTQR": {"type": "group", "description": "Traced molar Rate"}, - "GTCM": {"type": "group", "description": "Tracer Carrier molar Rate"}, - "GTMF": {"type": "group", "description": "Traced molar fraction"}, - "GTVL": {"type": "group", "description": "Traced liquid volume rate"}, - "GTVV": {"type": "group", "description": "Traced vapor volume rate"}, - "GTTL": {"type": "group", "description": "Traced liquid volume total"}, - "GTTV": {"type": "group", "description": "Traced vapor volume total"}, - "GTML": {"type": "group", "description": "Traced mass liquid rate"}, - "GTMV": {"type": "group", "description": "Traced mass vapor rate"}, - "GTLM": {"type": "group", "description": "Traced mass liquid total"}, - "GTVM": {"type": "group", "description": "Traced mass vapor total"}, - "GAPI": {"type": "group", "description": "Oil API"}, - "GSPR": {"type": "group", "description": "Salt Production Rate"}, - "GSPT": {"type": "group", "description": "Salt Production Total"}, - "GSIR": {"type": "group", "description": "Salt Injection Rate"}, - "GSIT": {"type": "group", "description": "Salt Injection Total"}, - "GSPC": {"type": "group", "description": "Salt Production Concentration"}, - "GSIC": {"type": "group", "description": "Salt Injection Concentration"}, - "WTPRANI": {"type": "group", "description": "Anion Production Rate"}, - "WTPTANI": {"type": "group", "description": "Anion Production Total"}, - "WTIRANI": {"type": "group", "description": "Anion Injection Rate"}, - "WTITANI": {"type": "group", "description": "Anion Injection Total"}, - "WTPRCAT": {"type": "group", "description": "Cation Production Rate"}, - "WTPTCAT": {"type": "group", "description": "Cation Production Total"}, - "WTIRCAT": {"type": "group", "description": "Cation Injection Rate"}, - "WTITCAT": {"type": "group", "description": "Cation Injection Total"}, - "GTPCHEA": {"type": "group", "description": "Production Temperature"}, - "GTICHEA": {"type": "group", "description": "Injection Temperature"}, - "GTPRHEA": {"type": "group", "description": "Energy flows"}, - "GTPTHEA": {"type": "group", "description": "Energy Production Total"}, - "GTIRHEA": {"type": "group", "description": "Energy flows"}, - "GTITHEA": {"type": "group", "description": "Energy Injection Total"}, - "GTIRF": {"type": "group", "description": "Tracer Injection Rate"}, - "GTIRS": {"type": "group", "description": "Tracer Injection Rate"}, - "GTPRF": {"type": "group", "description": "Tracer Production Rate"}, - "GTPRS": {"type": "group", "description": "Tracer Production Rate"}, - "GTITF": {"type": "group", "description": "Tracer Injection Total"}, - "GTITS": {"type": "group", "description": "Tracer Injection Total"}, - "GTPTF": {"type": "group", "description": "Tracer Production Total"}, - "GTPTS": {"type": "group", "description": "Tracer Production Total"}, - "GTICF": {"type": "group", "description": "Tracer Injection Concentration"}, - "GTICS": {"type": "group", "description": "Tracer Injection Concentration"}, - "GTPCF": {"type": "group", "description": "Tracer Production"}, - "GTPCS": {"type": "group", "description": "Tracer Production"}, - "GMPR": {"type": "group", "description": "Methane Production Rate"}, - "GMPT": {"type": "group", "description": "Methane Production Total"}, - "GMIR": {"type": "group", "description": "Methane Injection Rate"}, - "GMIT": {"type": "group", "description": "Methane Injection Total"}, - "GTPRFOA": {"type": "group", "description": "Production Rate"}, - "GTPTFOA": {"type": "group", "description": "Production Total"}, - "GTIRFOA": {"type": "group", "description": "Injection Rate"}, - "GTITFOA": {"type": "group", "description": "Injection Total"}, - "GGDC": {"type": "group", "description": "Gas Delivery Capacity"}, - "GGDCQ": {"type": "group", "description": "Field/Group Gas DCQ"}, - "GMCPL": {"type": "group", "description": "Group Multi-level Compressor Level"}, - "GPR": {"type": "group", "description": "Group nodal Pressure in network"}, - "GPRDC": {"type": "group", "description": "Group Pressure at Delivery Capacity"}, - "GPRFP": {"type": "group", "description": "Group or node Pressure in network from end of First Pass"}, - "GGPRNBFP": {"type": "group", "description": "Gas flow rate along Group's or node's outlet branch in network, from end of First Pass"}, - "GGCV": {"type": "group", "description": "Gas Calorific Value"}, - "GESR": {"type": "group", "description": "Energy Sales Rate"}, - "GEST": {"type": "group", "description": "Energy Sales Total"}, - "GEDC": {"type": "group", "description": "Energy Delivery Capacity"}, - "GEDCQ": {"type": "group", "description": "Energy DCQ"}, - "GPRG": {"type": "group", "description": "Group or node Pressure in the gas injection network"}, - "GPRW": {"type": "group", "description": "Group or node Pressure in the water injection network"}, - "GPRB": {"type": "group", "description": "Pressure drop along the group's or node's outlet branch in the production network"}, - "GPRBG": {"type": "group", "description": "Pressure drop along the group's or node's inlet branch in the gas injection network"}, - "GPRBW": {"type": "group", "description": "Pressure drop along the group's or node's inlet branch in the water injection network"}, - "GALQ": {"type": "group", "description": "ALQ in the group's or node's outlet branch in the production network"}, - "GOPRNB": {"type": "group", "description": "Oil flow rate along the group's or node's outlet branch in the production network"}, - "GWPRNB": {"type": "group", "description": "Water flow rate along the group's or node's outlet branch in the production network"}, - "GGPRNB": {"type": "group", "description": "Gas flow rate along the group's or node's outlet branch in the production network"}, - "GLPRNB": {"type": "group", "description": "Liquid flow rate along the group's or node's outlet branch in the production network"}, - "GWIRNB": {"type": "group", "description": "Water flow rate along the group's or node's inlet branch in the water injection network"}, - "GGIRNB": {"type": "group", "description": "Gas flow rate along the group's or node's inlet branch in the gas injection network"}, - "GOMNR": {"type": "group", "description": "Group or node minimum oil rate as specified with GNETDP in the production network"}, - "GGMNR": {"type": "group", "description": "Group or node minimum gas rate as specified with GNETDP in the production network"}, - "GWMNR": {"type": "group", "description": "Group or node minimum water rate as specified with GNETDP in the production network"}, - "GLMNR": {"type": "group", "description": "Group or node minimum liquid rate as specified with GNETDP in the production network"}, - "GOMXR": {"type": "group", "description": "Group or node maximum oil rate as specified with GNETDP in the production network"}, - "GGMXR": {"type": "group", "description": "Group or node maximum gas rate as specified with GNETDP in the production network"}, - "GWMXR": {"type": "group", "description": "Group or node maximum water rate as specified with GNETDP in the production network"}, - "GLMXR": {"type": "group", "description": "Group or node maximum liquid rate as specified with GNETDP in the production network"}, - "GMNP": {"type": "group", "description": "Group or node minimum pressure as specified with GNETDP in the production network"}, - "GMXP": {"type": "group", "description": "Group or node maximum pressure as specified with GNETDP in the production network"}, - "GPRINC": {"type": "group", "description": "Group or node pressure increment as specified with GNETDP in the production network"}, - "GPRDEC": {"type": "group", "description": "Group or node pressure decrement as specified with GNETDP in the production network"}, - "GCPR": {"type": "group", "description": "Polymer Production Rate"}, - "GCPC": {"type": "group", "description": "Polymer Production Concentration"}, - "GCPT": {"type": "group", "description": "Polymer Production Total"}, - "GCIR": {"type": "group", "description": "Polymer Injection Rate"}, - "GCIC": {"type": "group", "description": "Polymer Injection Concentration"}, - "GCIT": {"type": "group", "description": "Polymer Injection Total"}, - "GNPR": {"type": "group", "description": "Solvent Production Rate"}, - "GNPT": {"type": "group", "description": "Solvent Production Total"}, - "GNIR": {"type": "group", "description": "Solvent Injection Rate"}, - "GNIT": {"type": "group", "description": "Solvent Injection Total"}, - "GTPRSUR": {"type": "group", "description": "Production Rate"}, - "GTPTSUR": {"type": "group", "description": "Production Total"}, - "GTIRSUR": {"type": "group", "description": "Injection Rate"}, - "GTITSUR": {"type": "group", "description": "Injection Total"}, - "GTPRALK": {"type": "group", "description": "Production Rate"}, - "GTPTALK": {"type": "group", "description": "Production Total"}, - "GTIRALK": {"type": "group", "description": "Injection Rate"}, - "GTITALK": {"type": "group", "description": "Injection Total"}, - "GU": {"type": "group", "description": "User-defined group quantity"}, - "WOPR": {"type": "well", "description": "Oil Production Rate"}, - "WOPRA": {"type": "well", "description": "Oil Production Rate above GOC"}, - "WOPRB": {"type": "well", "description": "Oil Production Rate below GOC"}, - "WOPTA": {"type": "well", "description": "Oil Production Total above GOC"}, - "WOPTB": {"type": "well", "description": "Oil Production Total below GOC"}, - "WOPR1": {"type": "well", "description": "Oil Production Rate above GOC"}, - "WOPR2": {"type": "well", "description": "Oil Production Rate below GOC"}, - "WOPT1": {"type": "well", "description": "Oil Production Total above GOC"}, - "WOPT2": {"type": "well", "description": "Oil Production Total below GOC"}, - "WOMR": {"type": "well", "description": "Oil Mass Rate"}, - "WOMT": {"type": "well", "description": "Oil Mass Total"}, - "WODN": {"type": "well", "description": "Oil Density at Surface Conditions"}, - "WOPRH": {"type": "well", "description": "Oil Production Rate History"}, - "WOPRT": {"type": "well", "description": "Oil Production Rate Target/Limit"}, - "WOPRF": {"type": "well", "description": "Free Oil Production Rate"}, - "WOPRS": {"type": "well", "description": "Solution Oil Production Rate"}, - "WOPT": {"type": "well", "description": "Oil Production Total"}, - "WOPTH": {"type": "well", "description": "Oil Production Total History"}, - "WOPTF": {"type": "well", "description": "Free Oil Production Total"}, - "WOPTS": {"type": "well", "description": "Solution Oil Production Total"}, - "WOIR": {"type": "well", "description": "Oil Injection Rate"}, - "WOIRH": {"type": "well", "description": "Oil Injection Rate History"}, - "WOIRT": {"type": "well", "description": "Oil Injection Rate Target/Limit"}, - "WOIT": {"type": "well", "description": "Oil Injection Total"}, - "WOITH": {"type": "well", "description": "Oil Injection Total History"}, - "WOPP": {"type": "well", "description": "Oil Potential Production rate"}, - "WOPP2": {"type": "well", "description": "Oil Potential Production rate"}, - "WOPI": {"type": "well", "description": "Oil Potential Injection rate"}, - "WOPI2": {"type": "well", "description": "Oil Potential Injection rate"}, - "WOPGR": {"type": "well", "description": "Oil Production Guide Rate"}, - "WOIGR": {"type": "well", "description": "Oil Injection Guide Rate"}, - "WOVPR": {"type": "well", "description": "Oil Voidage Production Rate"}, - "WOVPT": {"type": "well", "description": "Oil Voidage Production Total"}, - "WOVIR": {"type": "well", "description": "Oil Voidage Injection Rate"}, - "WOVIT": {"type": "well", "description": "Oil Voidage Injection Total"}, - "WOnPR": {"type": "well", "description": "nth separator stage oil rate"}, - "WOnPT": {"type": "well", "description": "nth separator stage oil total"}, - "WWPR": {"type": "well", "description": "Water Production Rate"}, - "WWMR": {"type": "well", "description": "Water Mass Rate"}, - "WWMT": {"type": "well", "description": "Water Mass Total"}, - "WWPRH": {"type": "well", "description": "Water Production Rate History"}, - "WWPRT": {"type": "well", "description": "Water Production Rate Target/Limit"}, - "WWPT": {"type": "well", "description": "Water Production Total"}, - "WWPTH": {"type": "well", "description": "Water Production Total History"}, - "WWIR": {"type": "well", "description": "Water Injection Rate"}, - "WWIRH": {"type": "well", "description": "Water Injection Rate History"}, - "WWIRT": {"type": "well", "description": "Water Injection Rate Target/Limit"}, - "WWIT": {"type": "well", "description": "Water Injection Total"}, - "WWITH": {"type": "well", "description": "Water Injection Total History"}, - "WWPP": {"type": "well", "description": "Water Potential Production rate"}, - "WWPP2": {"type": "well", "description": "Water Potential Production rate"}, - "WWPI": {"type": "well", "description": "Water Potential Injection rate"}, - "WWIP": {"type": "well", "description": "Water Potential Injection rate"}, - "WWPI2": {"type": "well", "description": "Water Potential Injection rate"}, - "WWIP2": {"type": "well", "description": "Water Potential Injection rate"}, - "WWPGR": {"type": "well", "description": "Water Production Guide Rate"}, - "WWIGR": {"type": "well", "description": "Water Injection Guide Rate"}, - "WWVPR": {"type": "well", "description": "Water Voidage Production Rate"}, - "WWVPT": {"type": "well", "description": "Water Voidage Production Total"}, - "WWVIR": {"type": "well", "description": "Water Voidage Injection Rate"}, - "WWVIT": {"type": "well", "description": "Water Voidage Injection Total"}, - "WWPIR": {"type": "well", "description": "Ratio of produced water to injected water (percentage)"}, - "WWMPR": {"type": "well", "description": "Water component molar production rate"}, - "WWMPT": {"type": "well", "description": "Water component molar production total"}, - "WWMIR": {"type": "well", "description": "Water component molar injection rate"}, - "WWMIT": {"type": "well", "description": "Water component molar injection total"}, - "WGPR": {"type": "well", "description": "Gas Production Rate"}, - "WGPRA": {"type": "well", "description": "Gas Production Rate above"}, - "WGPRB": {"type": "well", "description": "Gas Production Rate below"}, - "WGPTA": {"type": "well", "description": "Gas Production Total above"}, - "WGPTB": {"type": "well", "description": "Gas Production Total below"}, - "WGPR1": {"type": "well", "description": "Gas Production Rate above GOC"}, - "WGPR2": {"type": "well", "description": "Gas Production Rate below GOC"}, - "WGPT1": {"type": "well", "description": "Gas Production Total above GOC"}, - "WGPT2": {"type": "well", "description": "Gas Production Total below GOC"}, - "WGMR": {"type": "well", "description": "Gas Mass Rate"}, - "WGMT": {"type": "well", "description": "Gas Mass Total"}, - "WGDN": {"type": "well", "description": "Gas Density at Surface Conditions"}, - "WGPRH": {"type": "well", "description": "Gas Production Rate History"}, - "WGPRT": {"type": "well", "description": "Gas Production Rate Target/Limit"}, - "WGPRF": {"type": "well", "description": "Free Gas Production Rate"}, - "WGPRS": {"type": "well", "description": "Solution Gas Production Rate"}, - "WGPT": {"type": "well", "description": "Gas Production Total"}, - "WGPTH": {"type": "well", "description": "Gas Production Total History"}, - "WGPTF": {"type": "well", "description": "Free Gas Production Total"}, - "WGPTS": {"type": "well", "description": "Solution Gas Production Total"}, - "WGIR": {"type": "well", "description": "Gas Injection Rate"}, - "WGIRH": {"type": "well", "description": "Gas Injection Rate History"}, - "WGIRT": {"type": "well", "description": "Gas Injection Rate Target/Limit"}, - "WGIT": {"type": "well", "description": "Gas Injection Total"}, - "WGITH": {"type": "well", "description": "Gas Injection Total History"}, - "WGPP": {"type": "well", "description": "Gas Potential Production rate"}, - "WGPP2": {"type": "well", "description": "Gas Potential Production rate"}, - "WGPPS": {"type": "well", "description": "Solution"}, - "WGPPS2": {"type": "well", "description": "Solution"}, - "WGPPF": {"type": "well", "description": "Free Gas Potential Production rate"}, - "WGPPF2": {"type": "well", "description": "Free Gas Potential Production rate"}, - "WGPI": {"type": "well", "description": "Gas Potential Injection rate"}, - "WGIP": {"type": "well", "description": "Gas Potential Injection rate"}, - "WGPI2": {"type": "well", "description": "Gas Potential Injection rate"}, - "WGIP2": {"type": "well", "description": "Gas Potential Injection rate"}, - "WGPGR": {"type": "well", "description": "Gas Production Guide Rate"}, - "WGIGR": {"type": "well", "description": "Gas Injection Guide Rate"}, - "WGLIR": {"type": "well", "description": "Gas Lift Injection Rate"}, - "WWGPR": {"type": "well", "description": "Wet Gas Production Rate"}, - "WWGPT": {"type": "well", "description": "Wet Gas Production Total"}, - "WWGPRH": {"type": "well", "description": "Wet Gas Production Rate History"}, - "WWGIR": {"type": "well", "description": "Wet Gas Injection Rate"}, - "WWGIT": {"type": "well", "description": "Wet Gas Injection Total"}, - "WGnPR": {"type": "well", "description": "nth separator stage gas rate"}, - "WGnPT": {"type": "well", "description": "nth separator stage gas total"}, - "WGVPR": {"type": "well", "description": "Gas Voidage Production Rate"}, - "WGVPT": {"type": "well", "description": "Gas Voidage Production Total"}, - "WGVIR": {"type": "well", "description": "Gas Voidage Injection Rate"}, - "WGVIT": {"type": "well", "description": "Gas Voidage Injection Total"}, - "WGQ": {"type": "well", "description": "Gas Quality"}, - "WLPR": {"type": "well", "description": "Liquid Production Rate"}, - "WLPRH": {"type": "well", "description": "Liquid Production Rate History"}, - "WLPRT": {"type": "well", "description": "Liquid Production Rate Target/Limit"}, - "WLPT": {"type": "well", "description": "Liquid Production Total"}, - "WLPTH": {"type": "well", "description": "Liquid Production Total History"}, - "WVPR": {"type": "well", "description": "Res Volume Production Rate"}, - "WVPRT": {"type": "well", "description": "Res Volume Production Rate Target/Limit"}, - "WVPT": {"type": "well", "description": "Res Volume Production Total"}, - "WVPGR": {"type": "well", "description": "Res Volume Production Guide Rate"}, - "WVIR": {"type": "well", "description": "Res Volume Injection Rate"}, - "WVIRT": {"type": "well", "description": "Res Volume Injection Rate Target/Limit"}, - "WVIT": {"type": "well", "description": "Res Volume Injection Total"}, - "WWCT": {"type": "well", "description": "Water Cut"}, - "WWCTH": {"type": "well", "description": "Water Cut History"}, - "WGOR": {"type": "well", "description": "Gas-Oil Ratio"}, - "WGORH": {"type": "well", "description": "Gas-Oil Ratio History"}, - "WOGR": {"type": "well", "description": "Oil-Gas Ratio"}, - "WOGRH": {"type": "well", "description": "Oil-Gas Ratio History"}, - "WWGR": {"type": "well", "description": "Water-Gas Ratio"}, - "WWGRH": {"type": "well", "description": "Water-Gas Ratio History"}, - "WGLR": {"type": "well", "description": "Gas-Liquid Ratio"}, - "WGLRH": {"type": "well", "description": "Gas-Liquid Ratio History"}, - "WBGLR": {"type": "well", "description": "Bottom hole Gas-Liquid Ratio"}, - "WBHP": {"type": "well", "description": "Bottom Hole Pressure"}, - "WBHPH": {"type": "well", "description": "Bottom Hole Pressure History,"}, - "WBHPT": {"type": "well", "description": "Bottom Hole Pressure Target/Limit"}, - "WTHP": {"type": "well", "description": "Tubing Head Pressure"}, - "WTHPH": {"type": "well", "description": "Tubing Head Pressure History,"}, - "WPI": {"type": "well", "description": "Productivity Index of well's preferred phase"}, - "WPIO": {"type": "well", "description": "Oil phase PI"}, - "WPIG": {"type": "well", "description": "Gas phase PI"}, - "WPIW": {"type": "well", "description": "Water phase PI"}, - "WPIL": {"type": "well", "description": "Liquid phase PI"}, - "WBP": {"type": "well", "description": "One-point Pressure Average"}, - "WBP4": {"type": "well", "description": "Four-point Pressure Average"}, - "WBP5": {"type": "well", "description": "Five-point Pressure Average"}, - "WBP9": {"type": "well", "description": "Nine-point Pressure Average"}, - "WPI1": {"type": "well", "description": "Productivity Index based on the value of WBP"}, - "WPI4": {"type": "well", "description": "Productivity Index based on the value of WBP4"}, - "WPI5": {"type": "well", "description": "Productivity Index based on the value of WBP5"}, - "WPI9": {"type": "well", "description": "Productivity Index based on the value of WBP9"}, - "WHD": {"type": "well", "description": "Hydraulic head in well based on the reference depth given in HYDRHEAD and the well's reference depth"}, - "WHDF": {"type": "well", "description": "Hydraulic head in well based on the reference depth given in HYDRHEAD and the well's reference depth calculated at freshwater conditions"}, - "WSTAT": {"type": "well", "description": "Well State Indicator"}, - "WMCTL": {"type": "well", "description": "Mode of Control"}, - "WMCON": {"type": "well", "description": "The number of connections capable of flowing in the well"}, - "WEPR": {"type": "well", "description": "Energy Production Rate"}, - "WEPT": {"type": "well", "description": "Energy Production Total"}, - "WEFF": {"type": "well", "description": "Efficiency Factor"}, - "WEFFG": {"type": "well", "description": "Product of efficiency factors of the well and all its superior groups"}, - "WALQ": {"type": "well", "description": "Well Artificial Lift Quantity"}, - "WMVFP": {"type": "well", "description": "VFP table number used by the well"}, - "WNLPR": {"type": "well", "description": "NGL Production Rate"}, - "WNLPT": {"type": "well", "description": "NGL Production Total"}, - "WNLPRH": {"type": "well", "description": "NGL Production Rate History"}, - "WNLPTH": {"type": "well", "description": "NGL Production Total History"}, - "WNLPRT": {"type": "well", "description": "NGL Production Rate Target"}, - "WAMF": {"type": "well", "description": "Component aqueous mole fraction, from producing completions"}, - "WXMF": {"type": "well", "description": "Liquid Mole Fraction"}, - "WYMF": {"type": "well", "description": "Vapor Mole Fraction"}, - "WXMFn": {"type": "well", "description": "Liquid Mole Fraction for nth separator stage"}, - "WYMFn": {"type": "well", "description": "Vapor Mole Fraction for nth separator stage"}, - "WZMF": {"type": "well", "description": "Total Mole Fraction"}, - "WCMPR": {"type": "well", "description": "Hydrocarbon Component Molar Production Rates"}, - "WCMPT": {"type": "well", "description": "Hydrocarbon Component"}, - "WCMIR": {"type": "well", "description": "Hydrocarbon Component Molar Injection Rates"}, - "WCMIT": {"type": "well", "description": "Hydrocarbon Component Molar Injection Totals"}, - "WCGIR": {"type": "well", "description": "Hydrocarbon Component Gas Injection Rate"}, - "WCGPR": {"type": "well", "description": "Hydrocarbon Component Gas Production Rate"}, - "WCOPR": {"type": "well", "description": "Hydrocarbon Component Oil Production Rate"}, - "WHMIR": {"type": "well", "description": "Hydrocarbon Molar Injection Rate"}, - "WHMIT": {"type": "well", "description": "Hydrocarbon Molar Injection Total"}, - "WHMPR": {"type": "well", "description": "Hydrocarbon Molar Production Rate"}, - "WHMPT": {"type": "well", "description": "Hydrocarbon Molar Production Total"}, - "WCHMR": {"type": "well", "description": "Hydrocarbon Component"}, - "WCHMT": {"type": "well", "description": "Hydrocarbon Component"}, - "WCWGPR": {"type": "well", "description": "Hydrocarbon Component Wet Gas Production Rate"}, - "WCWGPT": {"type": "well", "description": "Hydrocarbon Component Wet Gas Production Total"}, - "WCWGIR": {"type": "well", "description": "Hydrocarbon Component Wet Gas Injection Rate"}, - "WCWGIT": {"type": "well", "description": "Hydrocarbon Component Wet Gas Injection Total"}, - "WCGMR": {"type": "well", "description": "Hydrocarbon component"}, - "WCGMT": {"type": "well", "description": "Hydrocarbon component"}, - "WCOMR": {"type": "well", "description": "Hydrocarbon component"}, - "WCOMT": {"type": "well", "description": "Hydrocarbon component"}, - "WCNMR": {"type": "well", "description": "Hydrocarbon component molar rates in the NGL phase"}, - "WCNWR": {"type": "well", "description": "Hydrocarbon component mass rates in the NGL phase"}, - "WCGMRn": {"type": "well", "description": "Hydrocarbon component molar rates in the gas phase for nth separator stage"}, - "WCGRn": {"type": "well", "description": "Hydrocarbon component molar rates in the gas phase for nth separator stage"}, - "WCOMRn": {"type": "well", "description": "Hydrocarbon component"}, - "WCORn": {"type": "well", "description": "Hydrocarbon component"}, - "WMUF": {"type": "well", "description": "Make-up fraction"}, - "WTHT": {"type": "well", "description": "Tubing Head Temperature"}, - "WMMW": {"type": "well", "description": "Mean molecular weight of wellstream"}, - "WPWE0": {"type": "well", "description": "Well drilled indicator"}, - "WPWE1": {"type": "well", "description": "Connections opened indicator"}, - "WPWE2": {"type": "well", "description": "Connections closed indicator"}, - "WPWE3": {"type": "well", "description": "Connections closed to bottom indicator"}, - "WPWE4": {"type": "well", "description": "Well stopped indicator"}, - "WPWE5": {"type": "well", "description": "Injector to producer indicator"}, - "WPWE6": {"type": "well", "description": "Producer to injector indicator"}, - "WPWE7": {"type": "well", "description": "Well shut indicator"}, - "WPWEM": {"type": "well", "description": "WELEVNT output mnemonic"}, - "WDRPR": {"type": "well", "description": "Well drilling priority"}, - "WBHWCn": {"type": "well", "description": "Derivative of well BHP with respect to parameter n"}, - "WGFWCn": {"type": "well", "description": "Derivative of well gas flow rate with respect to parameter n"}, - "WOFWCn": {"type": "well", "description": "Derivative of well oil flow rate with respect to parameter n"}, - "WWFWCn": {"type": "well", "description": "Derivative of water flow rate with respect to parameter n"}, - "WTPR": {"type": "well", "description": "Tracer Production Rate"}, - "WTPT": {"type": "well", "description": "Tracer Production Total"}, - "WTPC": {"type": "well", "description": "Tracer Production Concentration"}, - "WTIR": {"type": "well", "description": "Tracer Injection Rate"}, - "WTIT": {"type": "well", "description": "Tracer Injection Total"}, - "WTIC": {"type": "well", "description": "Tracer Injection Concentration"}, - "WTMR": {"type": "well", "description": "Traced mass Rate"}, - "WTMT": {"type": "well", "description": "Traced mass Total"}, - "WTQR": {"type": "well", "description": "Traced molar Rate"}, - "WTCM": {"type": "well", "description": "Tracer Carrier molar Rate"}, - "WTMF": {"type": "well", "description": "Traced molar fraction"}, - "WTVL": {"type": "well", "description": "Traced liquid volume rate"}, - "WTVV": {"type": "well", "description": "Traced vapor volume rate"}, - "WTTL": {"type": "well", "description": "Traced liquid volume total"}, - "WTTV": {"type": "well", "description": "Traced vapor volume total"}, - "WTML": {"type": "well", "description": "Traced mass liquid rate"}, - "WTMV": {"type": "well", "description": "Traced mass vapor rate"}, - "WTLM": {"type": "well", "description": "Traced mass liquid total"}, - "WTVM": {"type": "well", "description": "Traced mass vapor total"}, - "WAPI": {"type": "well", "description": "Oil API"}, - "WSPR": {"type": "well", "description": "Salt Production Rate"}, - "WSPT": {"type": "well", "description": "Salt Production Total"}, - "WSIR": {"type": "well", "description": "Salt Injection Rate"}, - "WSIT": {"type": "well", "description": "Salt Injection Total"}, - "WSPC": {"type": "well", "description": "Salt Production Concentration"}, - "WSIC": {"type": "well", "description": "Salt Injection Concentration"}, - "WTPCHEA": {"type": "well", "description": "Production Temperature"}, - "WTICHEA": {"type": "well", "description": "Injection Temperature"}, - "WTPRHEA": {"type": "well", "description": "Energy flows"}, - "WTPTHEA": {"type": "well", "description": "Energy Production Total"}, - "WTIRHEA": {"type": "well", "description": "Energy flows"}, - "WTITHEA": {"type": "well", "description": "Energy Injection Total"}, - "WTIRF": {"type": "well", "description": "Tracer Injection Rate"}, - "WTIRS": {"type": "well", "description": "Tracer Injection Rate"}, - "WTPRF": {"type": "well", "description": "Tracer Production Rate"}, - "WTPRS": {"type": "well", "description": "Tracer Production Rate"}, - "WTITF": {"type": "well", "description": "Tracer Injection Total"}, - "WTITS": {"type": "well", "description": "Tracer Injection Total"}, - "WTPTF": {"type": "well", "description": "Tracer Production Total"}, - "WTPTS": {"type": "well", "description": "Tracer Production Total"}, - "WTICF": {"type": "well", "description": "Tracer Injection Concentration"}, - "WTICS": {"type": "well", "description": "Tracer Injection Concentration"}, - "WTPCF": {"type": "well", "description": "Tracer Production"}, - "WTPCS": {"type": "well", "description": "Tracer Production"}, - "WMPR": {"type": "well", "description": "Methane Production Rate"}, - "WMPT": {"type": "well", "description": "Methane Production Total"}, - "WMIR": {"type": "well", "description": "Methane Injection Rate"}, - "WMIT": {"type": "well", "description": "Methane Injection Total"}, - "WTPRFOA": {"type": "well", "description": "Production Rate"}, - "WTPTFOA": {"type": "well", "description": "Production Total"}, - "WTIRFOA": {"type": "well", "description": "Injection Rate"}, - "WTITFOA": {"type": "well", "description": "Injection Total"}, - "WGDC": {"type": "well", "description": "Gas Delivery Capacity"}, - "NGOPAS": {"type": "well", "description": "Number of iterations to converge DCQ in first pass"}, - "WGPRFP": {"type": "well", "description": "Well Gas Production Rate from end of First Pass"}, - "WTHPFP": {"type": "well", "description": "Well Tubing Head Pressure from end of First Pass"}, - "WBHPFP": {"type": "well", "description": "Well Bottom Hole Pressure from end of First Pass"}, - "WOGLR": {"type": "well", "description": "Well Oil Gas Lift Ratio"}, - "WGCV": {"type": "well", "description": "Gas Calorific Value"}, - "WEDC": {"type": "well", "description": "Energy Delivery Capacity"}, - "WCPR": {"type": "well", "description": "Polymer Production Rate"}, - "WCPC": {"type": "well", "description": "Polymer Production Concentration"}, - "WCPT": {"type": "well", "description": "Polymer Production Total"}, - "WCIR": {"type": "well", "description": "Polymer Injection Rate"}, - "WCIC": {"type": "well", "description": "Polymer Injection Concentration"}, - "WCIT": {"type": "well", "description": "Polymer Injection Total"}, - "WNPR": {"type": "well", "description": "Solvent Production Rate"}, - "WNPT": {"type": "well", "description": "Solvent Production Total"}, - "WNIR": {"type": "well", "description": "Solvent Injection Rate"}, - "WNIT": {"type": "well", "description": "Solvent Injection Total"}, - "WTPRSUR": {"type": "well", "description": "Production Rate"}, - "WTPTSUR": {"type": "well", "description": "Production Total"}, - "WTIRSUR": {"type": "well", "description": "Injection Rate"}, - "WTITSUR": {"type": "well", "description": "Injection Total"}, - "WTPRALK": {"type": "well", "description": "Production Rate"}, - "WTPTALK": {"type": "well", "description": "Production Total"}, - "WTIRALK": {"type": "well", "description": "Injection Rate"}, - "WTITALK": {"type": "well", "description": "Injection Total"}, - "WU": {"type": "well", "description": "User-defined well quantity"}, - "COFR": {"type": "completion", "description": "Oil Flow Rate"}, - "COFRF": {"type": "completion", "description": "Free Oil Flow Rate"}, - "COFRS": {"type": "completion", "description": "Solution oil flow rate"}, - "COFRU": {"type": "completion", "description": "Sum of connection oil flow rates upstream of, and including, this connection"}, - "COPR": {"type": "completion", "description": "Oil Production Rate"}, - "COPT": {"type": "completion", "description": "Oil Production Total"}, - "COPTF": {"type": "completion", "description": "Free Oil Production Total"}, - "COPTS": {"type": "completion", "description": "Solution Oil Production Total"}, - "COIT": {"type": "completion", "description": "Oil Injection Total"}, - "COPP": {"type": "completion", "description": "Oil Potential Production rate"}, - "COPI": {"type": "completion", "description": "Oil Potential Injection rate"}, - "CWFR": {"type": "completion", "description": "Water Flow Rate"}, - "CWFRU": {"type": "completion", "description": "Sum of connection water flow rates upstream of, and including, this connection"}, - "CWPR": {"type": "completion", "description": "Water Production Rate"}, - "CWPT": {"type": "completion", "description": "Water Production Total"}, - "CWIR": {"type": "completion", "description": "Water Injection Rate"}, - "CWIT": {"type": "completion", "description": "Water Injection Total"}, - "CWPP": {"type": "completion", "description": "Water Potential Production rate"}, - "CWPI": {"type": "completion", "description": "Water Potential Injection rate"}, - "CGFR": {"type": "completion", "description": "Gas Flow Rate"}, - "CGFRF": {"type": "completion", "description": "Free Gas Flow Rate"}, - "CGFRS": {"type": "completion", "description": "Solution Gas Flow Rate"}, - "CGFRU": {"type": "completion", "description": "Sum of connection gas flow rates upstream of, and including, this connection"}, - "CGPR": {"type": "completion", "description": "Gas Production Rate "}, - "CGPT": {"type": "completion", "description": "Gas Production Total"}, - "CGPTF": {"type": "completion", "description": "Free Gas Production Total"}, - "CGPTS": {"type": "completion", "description": "Solution Gas Production Total"}, - "CGIR": {"type": "completion", "description": "Gas Injection Rate"}, - "CGIT": {"type": "completion", "description": "Gas Injection Total"}, - "CGPP": {"type": "completion", "description": "Gas Potential Production rate"}, - "CGPI": {"type": "completion", "description": "Gas Potential Injection rate"}, - "CGQ": {"type": "completion", "description": "Gas Quality"}, - "CLFR": {"type": "completion", "description": "Liquid Flow Rate"}, - "CLPT": {"type": "completion", "description": "Liquid Production Total"}, - "CVFR": {"type": "completion", "description": "Reservoir"}, - "CVPR": {"type": "completion", "description": "Res Volume Production Rate"}, - "CVPT": {"type": "completion", "description": "Res Volume Production Total"}, - "CVIR": {"type": "completion", "description": "Res Volume Injection Rate"}, - "CVIT": {"type": "completion", "description": "Res Volume Injection Total"}, - "CWCT": {"type": "completion", "description": "Water Cut"}, - "CGOR": {"type": "completion", "description": "Gas-Oil Ratio"}, - "COGR": {"type": "completion", "description": "Oil-Gas Ratio"}, - "CWGR": {"type": "completion", "description": "Water-Gas Ratio"}, - "CGLR": {"type": "completion", "description": "Gas-Liquid Ratio"}, - "CPR": {"type": "completion", "description": "Connection Pressure"}, - "CPI": {"type": "completion", "description": "Productivity Index of well's preferred phase"}, - "CTFAC": {"type": "completion", "description": "Connection Transmissibility Factor"}, - "CDBF": {"type": "completion", "description": "Blocking factor for generalized pseudo-pressure method"}, - "CGPPTN": {"type": "completion", "description": "Generalized pseudo-pressure table update counter"}, - "CGPPTS": {"type": "completion", "description": "Generalized pseudo-pressure table update status"}, - "CDSM": {"type": "completion", "description": "Current mass of scale deposited"}, - "CDSML": {"type": "completion", "description": "Current mass of scale deposited per unit perforation length"}, - "CDSF": {"type": "completion", "description": "PI multiplicative factor due to scale damage"}, - "CAMF": {"type": "completion", "description": "Component aqueous mole fraction, from producing completions"}, - "CZMF": {"type": "completion", "description": "Total Mole Fraction"}, - "CKFR": {"type": "completion", "description": "Hydrocarbon Component"}, - "CKFT": {"type": "completion", "description": "Hydrocarbon Component"}, - "CDFAC": {"type": "completion", "description": "D-factor for flow dependent skin factor"}, - "CTFR": {"type": "completion", "description": "Tracer Flow Rate"}, - "CTPR": {"type": "completion", "description": "Tracer Production Rate"}, - "CTPT": {"type": "completion", "description": "Tracer Production Total"}, - "CTPC": {"type": "completion", "description": "Tracer Production Concentration"}, - "CTIR": {"type": "completion", "description": "Tracer Injection Rate"}, - "CTIT": {"type": "completion", "description": "Tracer Injection Total"}, - "CTIC": {"type": "completion", "description": "Tracer Injection Concentration"}, - "CAPI": {"type": "completion", "description": "Oil API"}, - "CSFR": {"type": "completion", "description": "Salt Flow Rate"}, - "CSPR": {"type": "completion", "description": "Salt Production Rate"}, - "CSPT": {"type": "completion", "description": "Salt Production Total"}, - "CSIR": {"type": "completion", "description": "Salt Injection Rate"}, - "CSIT": {"type": "completion", "description": "Salt Injection Total"}, - "CSPC": {"type": "completion", "description": "Salt Production Concentration"}, - "CSIC": {"type": "completion", "description": "Salt Injection Concentration"}, - "CTFRANI": {"type": "completion", "description": "Anion Flow Rate"}, - "CTPTANI": {"type": "completion", "description": "Anion Production Total"}, - "CTITANI": {"type": "completion", "description": "Anion Injection Total"}, - "CTFRCAT": {"type": "completion", "description": "Cation Flow Rate"}, - "CTPTCAT": {"type": "completion", "description": "Cation Production Total"}, - "CTITCAT": {"type": "completion", "description": "Cation Injection Total"}, - "CTIRF": {"type": "completion", "description": "Tracer Injection Rate"}, - "CTIRS": {"type": "completion", "description": "Tracer Injection Rate"}, - "CTPRF": {"type": "completion", "description": "Tracer Production Rate"}, - "CTPRS": {"type": "completion", "description": "Tracer Production Rate"}, - "CTITF": {"type": "completion", "description": "Tracer Injection Total"}, - "CTITS": {"type": "completion", "description": "Tracer Injection Total"}, - "CTPTF": {"type": "completion", "description": "Tracer Production Total"}, - "CTPTS": {"type": "completion", "description": "Tracer Production Total"}, - "CTICF": {"type": "completion", "description": "Tracer Injection Concentration"}, - "CTICS": {"type": "completion", "description": "Tracer Injection Concentration"}, - "CTPCF": {"type": "completion", "description": "Tracer Production"}, - "CTPCS": {"type": "completion", "description": "Tracer Production"}, - "CTFRFOA": {"type": "completion", "description": "Flow Rate"}, - "CTPTFOA": {"type": "completion", "description": "Production Total"}, - "CTITFOA": {"type": "completion", "description": "Injection Total"}, - "CRREXCH": {"type": "completion", "description": "Exchange flux at current time"}, - "CRRPROT": {"type": "completion", "description": "Connection cumulative water production"}, - "CRRINJT": {"type": "completion", "description": "Connection cumulative water injection"}, - "CCFR": {"type": "completion", "description": "Polymer Flow Rate"}, - "CCPR": {"type": "completion", "description": "Polymer Production Rate"}, - "CCPC": {"type": "completion", "description": "Polymer Production Concentration"}, - "CCPT": {"type": "completion", "description": "Polymer Production Total"}, - "CCIR": {"type": "completion", "description": "Polymer Injection Rate"}, - "CCIC": {"type": "completion", "description": "Polymer Injection Concentration"}, - "CCIT": {"type": "completion", "description": "Polymer Injection Total"}, - "CNFR": {"type": "completion", "description": "Solvent Flow Rate"}, - "CNPT": {"type": "completion", "description": "Solvent Production Total"}, - "CNIT": {"type": "completion", "description": "Solvent Injection Total"}, - "CTFRSUR": {"type": "completion", "description": "Flow Rate"}, - "CTPTSUR": {"type": "completion", "description": "Production Total"}, - "CTITSUR": {"type": "completion", "description": "Injection Total"}, - "CTFRALK": {"type": "completion", "description": "Flow Rate"}, - "CTPTALK": {"type": "completion", "description": "Production Total"}, - "CTITALK": {"type": "completion", "description": "Injection Total"}, - "LCOFRU": {"type": "completion", "description": "As COFRU but for local grids"}, - "LCWFRU": {"type": "completion", "description": "As CWFRU but for local grids"}, - "LCGFRU": {"type": "completion", "description": "As CGFRU but for local grids"}, - "CU": {"type": "completion", "description": "User-defined connection quantity"}, - "COFRL": {"type": "completion", "description": "Oil Flow Rate"}, - "WOFRL": {"type": "completion", "description": "Oil Flow Rate"}, - "COPRL": {"type": "completion", "description": "Oil Flow Rate"}, - "WOPRL": {"type": "completion", "description": "Oil Flow Rate"}, - "COPTL": {"type": "completion", "description": "Oil Production Total"}, - "WOPTL": {"type": "completion", "description": "Oil Production Total"}, - "COITL": {"type": "completion", "description": "Oil Injection Total"}, - "WOITL": {"type": "completion", "description": "Oil Injection Total"}, - "CWFRL": {"type": "completion", "description": "Water Flow Rate"}, - "WWFRL": {"type": "completion", "description": "Water Flow Rate"}, - "CWPRL": {"type": "completion", "description": "Water Flow Rate"}, - "WWPRL": {"type": "completion", "description": "Water Flow Rate"}, - "CWPTL": {"type": "completion", "description": "Water Production Total"}, - "WWPTL": {"type": "completion", "description": "Water Production Total"}, - "CWIRL": {"type": "completion", "description": "Water Injection Rate"}, - "WWIRL": {"type": "completion", "description": "Water Injection Rate"}, - "CWITL": {"type": "completion", "description": "Water Injection Total"}, - "WWITL": {"type": "completion", "description": "Water Injection Total"}, - "CGFRL": {"type": "completion", "description": "Gas Flow Rate"}, - "WGFRL": {"type": "completion", "description": "Gas Flow Rate"}, - "CGPRL": {"type": "completion", "description": "Gas Flow Rate"}, - "WGPRL": {"type": "completion", "description": "Gas Flow Rate"}, - "CGPTL": {"type": "completion", "description": "Gas Production Total"}, - "WGPTL": {"type": "completion", "description": "Gas Production Total"}, - "CGIRL": {"type": "completion", "description": "Gas Injection Rate"}, - "WGIRL": {"type": "completion", "description": "Gas Injection Rate"}, - "CGITL": {"type": "completion", "description": "Gas Injection Total"}, - "WGITL": {"type": "completion", "description": "Gas Injection Total"}, - "CLFRL": {"type": "completion", "description": "Liquid Flow Rate"}, - "WLFRL": {"type": "completion", "description": "Liquid Flow Rate"}, - "CLPTL": {"type": "completion", "description": "Liquid Production Total"}, - "WLPTL": {"type": "completion", "description": "Liquid Production Total"}, - "CVFRL": {"type": "completion", "description": "Reservoir"}, - "WVFRL": {"type": "completion", "description": "Res Volume Flow Rate"}, - "CVPRL": {"type": "completion", "description": "Res Volume Production Flow Rate"}, - "WVPRL": {"type": "completion", "description": "Res Volume Production Flow Rate"}, - "CVIRL": {"type": "completion", "description": "Res Volume Injection Flow Rate"}, - "WVIRL": {"type": "completion", "description": "Res Volume Injection Flow Rate"}, - "CVPTL": {"type": "completion", "description": "Res Volume Production Total"}, - "WVPTL": {"type": "completion", "description": "Res Volume Production Total"}, - "CVITL": {"type": "completion", "description": "Res Volume Injection Total"}, - "WVITL": {"type": "completion", "description": "Res Volume Injection Total"}, - "CWCTL": {"type": "completion", "description": "Water Cut"}, - "WWCTL": {"type": "completion", "description": "Water Cut"}, - "CGORL": {"type": "completion", "description": "Gas-Oil Ratio"}, - "WGORL": {"type": "completion", "description": "Gas-Oil Ratio"}, - "COGRL": {"type": "completion", "description": "Oil-Gas Ratio"}, - "WOGRL": {"type": "completion", "description": "Oil-Gas Ratio"}, - "CWGRL": {"type": "completion", "description": "Water-Gas Ratio"}, - "WWGRL": {"type": "completion", "description": "Water-Gas Ratio"}, - "CGLRL": {"type": "completion", "description": "Gas-Liquid Ratio"}, - "WGLRL": {"type": "completion", "description": "Gas-Liquid Ratio"}, - "CPRL": {"type": "completion", "description": "Average Connection Pressure in completion"}, - "CKFRL": {"type": "completion", "description": "Hydrocarbon Component"}, - "CKFTL": {"type": "completion", "description": "Hydrocarbon Component"}, - "RPR": {"type": "region", "description": "Pressure average value"}, - "RPRH": {"type": "region", "description": "Pressure average value"}, - "RPRP": {"type": "region", "description": "Pressure average value"}, - "RPRGZ": {"type": "region", "description": "P/Z"}, - "RRS": {"type": "region", "description": "Gas-oil ratio"}, - "RRV": {"type": "region", "description": "Oil-gas ratio"}, - "RPPC": {"type": "region", "description": "Initial Contact Corrected Potential"}, - "RRPV": {"type": "region", "description": "Pore Volume at Reservoir conditions"}, - "ROPV": {"type": "region", "description": "Pore Volume containing Oil"}, - "RWPV": {"type": "region", "description": "Pore Volume containing Water"}, - "RGIP": {"type": "region", "description": "Gas In Place (liquid+gas phase)"}, - "RGIPG": {"type": "region", "description": "Gas In Place (gas phase)"}, - "RGIPL": {"type": "region", "description": "Gas In Place (liquid phase)"}, - "RGP": {"type": "region", "description": "Net Gas Production (injection subtracted)"}, - "RGPR": {"type": "region", "description": "Gas Production Rate"}, - "RGPRF": {"type": "region", "description": "Free Gas Production Rate"}, - "RGPRS": {"type": "region", "description": "Solution Gas Production Rate"}, - "RGPT": {"type": "region", "description": "Gas Production Total"}, - "RGPTF": {"type": "region", "description": "Free Gas Production Total"}, - "RGPTS": {"type": "region", "description": "Solution Gas Production Total"}, - "RGPV": {"type": "region", "description": "Pore Volume containing Gas"}, - "RGIR": {"type": "region", "description": "Gas Injection Rate"}, - "RGIT": {"type": "region", "description": "Gas Injection Total"}, - "RHPV": {"type": "region", "description": "Pore Volume containing Hydrocarbon"}, - "RRTM": {"type": "region", "description": "Transmissibility Multiplier associated with rock compaction"}, - "ROIP": {"type": "region", "description": "Oil In Place (liquid+gas phase)"}, - "ROIPG": {"type": "region", "description": "Oil In Place (gas phase)"}, - "ROIPL": {"type": "region", "description": "Oil In Place (liquid phase)"}, - "ROP": {"type": "region", "description": "Net Oil Production"}, - "ROPR": {"type": "region", "description": "Oil Production Rate"}, - "ROPRF": {"type": "region", "description": "Free Oil Production Rate"}, - "ROPRS": {"type": "region", "description": "Solution Oil Production Rate"}, - "ROPT": {"type": "region", "description": "Oil Production Total"}, - "ROPTF": {"type": "region", "description": "Free Oil Production Total"}, - "ROPTS": {"type": "region", "description": "Solution Oil Production Total"}, - "ROIR": {"type": "region", "description": "Oil Injection Rate"}, - "ROIT": {"type": "region", "description": "Oil Injection Total"}, - "RWP": {"type": "region", "description": "Net Water Production"}, - "RWPR": {"type": "region", "description": "Water Production Rate"}, - "RWPT": {"type": "region", "description": "Water Production Total"}, - "RWIP": {"type": "region", "description": "Water In Place"}, - "RWIR": {"type": "region", "description": "Water Injection Rate"}, - "RWIT": {"type": "region", "description": "Water Injection Total"}, - "ROE": {"type": "region", "description": "(OIP(initial) - OIP(now)) / OIP(initial)"}, - "ROEW": {"type": "region", "description": "Oil Production from Wells / OIP(initial)"}, - "ROEIW": {"type": "region", "description": "(OIP(initial) - OIP(now)) / Initial Mobile Oil with respect to Water"}, - "ROEWW": {"type": "region", "description": "Oil Production from Wells / Initial Mobile Oil with respect to Water"}, - "ROEIG": {"type": "region", "description": "(OIP(initial) - OIP(now)) / Initial Mobile Oil with respect to Gas"}, - "ROEWG": {"type": "region", "description": "Oil Production from Wells / Initial Mobile Oil with respect to Gas"}, - "RORMR": {"type": "region", "description": "Total stock tank oil produced by rock compaction"}, - "RORMW": {"type": "region", "description": "Total stock tank oil produced by water influx"}, - "RORMG": {"type": "region", "description": "Total stock tank oil produced by gas influx"}, - "RORME": {"type": "region", "description": "Total stock tank oil produced by oil expansion"}, - "RORMS": {"type": "region", "description": "Total stock tank oil produced by solution gas"}, - "RORMF": {"type": "region", "description": "Total stock tank oil produced by free gas influx"}, - "RORMX": {"type": "region", "description": "Total stock tank oil produced by 'traced' water influx"}, - "RORMY": {"type": "region", "description": "Total stock tank oil produced by other water influx"}, - "RORFR": {"type": "region", "description": "Fraction of total oil produced by rock compaction"}, - "RORFW": {"type": "region", "description": "Fraction of total oil produced by water influx"}, - "RORFG": {"type": "region", "description": "Fraction of total oil produced by gas influx"}, - "RORFE": {"type": "region", "description": "Fraction of total oil produced by oil expansion"}, - "RORFS": {"type": "region", "description": "Fraction of total oil produced by solution gas"}, - "RORFF": {"type": "region", "description": "Fraction of total oil produced by free gas influx"}, - "RORFX": {"type": "region", "description": "Fraction of total oil produced by 'traced' water influx"}, - "RORFY": {"type": "region", "description": "Fraction of total oil produced by other water influx"}, - "RTIPT": {"type": "region", "description": "Tracer In Place"}, - "RTIPF": {"type": "region", "description": "Tracer In Place"}, - "RTIPS": {"type": "region", "description": "Tracer In Place"}, - "RAPI": {"type": "region", "description": "Oil API"}, - "RSIP": {"type": "region", "description": "Salt In Place"}, - "RTIPTHEA": {"type": "region", "description": "Difference in Energy in place between current and initial time"}, - "RTIP#": {"type": "region", "description": "Tracer In Place in phase # (1,2,3,...)"}, - "RTADS": {"type": "region", "description": "Tracer Adsorption total"}, - "RTDCY": {"type": "region", "description": "Decayed tracer"}, - "RCGC": {"type": "region", "description": "Bulk Coal Gas Concentration"}, - "RCSC": {"type": "region", "description": "Bulk Coal Solvent Concentration"}, - "RTIPTFOA": {"type": "region", "description": "In Solution"}, - "RTADSFOA": {"type": "region", "description": "Adsorption total"}, - "RTDCYFOA": {"type": "region", "description": "Decayed tracer"}, - "RTMOBFOA": {"type": "region", "description": "Gas mobility factor"}, - "RCIP": {"type": "region", "description": "Polymer In Solution"}, - "RCAD": {"type": "region", "description": "Polymer Adsorption total"}, - "RNIP": {"type": "region", "description": "Solvent In Place"}, - "RTIPTSUR": {"type": "region", "description": "In Solution"}, - "RTADSUR": {"type": "region", "description": "Adsorption total"}, - "RU": {"type": "region", "description": "User-defined region quantity"}, - "ROFR": {"type": "region2region", "description": "Inter-region oil flow rate"}, - "ROFR+": {"type": "region2region", "description": "Inter-region oil flow rate"}, - "ROFR-": {"type": "region2region", "description": "Inter-region oil flow rate"}, - "ROFT": {"type": "region2region", "description": "Inter-region oil flow total"}, - "ROFT+": {"type": "region2region", "description": "Inter-region oil flow total"}, - "ROFT-": {"type": "region2region", "description": "Inter-region oil flow total"}, - "ROFTL": {"type": "region2region", "description": "Inter-region oil flow total"}, - "ROFTG": {"type": "region2region", "description": "Inter-region oil flow total"}, - "RGFR": {"type": "region2region", "description": "Inter-region gas flow rate"}, - "RGFR+": {"type": "region2region", "description": "Inter-region gas flow rate"}, - "RGFR-": {"type": "region2region", "description": "Inter-region gas flow rate"}, - "RGFT": {"type": "region2region", "description": "Inter-region gas flow total)"}, - "RGFT+": {"type": "region2region", "description": "Inter-region gas flow total"}, - "RGFT-": {"type": "region2region", "description": "Inter-region gas flow total"}, - "RGFTL": {"type": "region2region", "description": "Inter-region gas flow total"}, - "RGFTG": {"type": "region2region", "description": "Inter-region gas flow total"}, - "RWFR": {"type": "region2region", "description": "Inter-region water flow rate"}, - "RWFR+": {"type": "region2region", "description": "Inter-region water flow rate"}, - "RWFR-": {"type": "region2region", "description": "Inter-region water flow rate"}, - "RWFT": {"type": "region2region", "description": "Inter-region water flow total"}, - "RTFTF": {"type": "region2region", "description": "Tracer inter-region Flow Total"}, - "RTFTS": {"type": "region2region", "description": "Tracer inter-region Flow Total"}, - "RTFTT": {"type": "region2region", "description": "Tracer inter-region Flow Total"}, - "RSFT": {"type": "region2region", "description": "Salt inter-region Flow Total"}, - "RTFT#": {"type": "region2region", "description": "Tracer inter-region Flow in phase # (1,2,3,...)"}, - "RTFTTFOA": {"type": "region2region", "description": "Inter-region Flow Total"}, - "RCFT": {"type": "region2region", "description": "Polymer inter-region Flow Total"}, - "RNFT": {"type": "region2region", "description": "Solvent inter-region Flow"}, - "RTFTTSUR": {"type": "region2region", "description": "Inter-region Flow Total"}, - "BPR": {"type": "block", "description": "Oil phase Pressure"}, - "BPRESSUR": {"type": "block", "description": "Oil phase Pressure"}, - "BWPR": {"type": "block", "description": "Water phase Pressure"}, - "BGPR": {"type": "block", "description": "Gas phase Pressure"}, - "BRS": {"type": "block", "description": "Gas-oil ratio"}, - "BRV": {"type": "block", "description": "Oil-gas ratio"}, - "BPBUB": {"type": "block", "description": "Bubble point pressure"}, - "BPDEW": {"type": "block", "description": "Dew point pressure"}, - "BRSSAT": {"type": "block", "description": "Saturated gas-oil ratio"}, - "BRVSAT": {"type": "block", "description": "Saturated oil-gas ratio"}, - "BSTATE": {"type": "block", "description": "Gas-oil state indicator"}, - "BPPC": {"type": "block", "description": "Initial Contact Corrected Potential"}, - "BOIP": {"type": "block", "description": "Oil In Place (liquid+gas phase)"}, - "BOIPG": {"type": "block", "description": "Oil In Place (gas phase)"}, - "BOIPL": {"type": "block", "description": "Oil In Place (liquid phase)"}, - "BOKR": {"type": "block", "description": "Oil relative permeability"}, - "BWKR": {"type": "block", "description": "Water relative permeability"}, - "BGKR": {"type": "block", "description": "Gas relative permeability"}, - "BKRO": {"type": "block", "description": "Oil relative permeability"}, - "BKROG": {"type": "block", "description": "Two-phase oil relative permeability to gas"}, - "BKROW": {"type": "block", "description": "Two-phase oil relative permeability to water"}, - "BKRG": {"type": "block", "description": "Gas relative permeability"}, - "BKRGO": {"type": "block", "description": "Two-phase gas relative permeability to oil "}, - "BKRGW": {"type": "block", "description": "Two-phase gas relative permeability to water"}, - "BKRW": {"type": "block", "description": "Water relative permeability"}, - "BKRWG": {"type": "block", "description": "Two-phase water relative permeability to gas"}, - "BKRWO": {"type": "block", "description": "Two-phase water relative permeability to oil"}, - "BRK": {"type": "block", "description": "Water relative permeability reduction factor due to polymer"}, - "BEWKR": {"type": "block", "description": "Water effective relative permeability due to polymer"}, - "BWPC": {"type": "block", "description": "Water-Oil capillary pressure"}, - "BGPC": {"type": "block", "description": "Gas-Oil capillary pressure"}, - "BPCO": {"type": "block", "description": "Oil Capillary Pressures"}, - "BPCG": {"type": "block", "description": "Gas Capillary Pressures"}, - "BPCW": {"type": "block", "description": "Water Capillary Pressures"}, - "BGTRP": {"type": "block", "description": "Trapped gas saturation"}, - "BGTPD": {"type": "block", "description": "Dynamic trapped gas saturation"}, - "BGSHY": {"type": "block", "description": "Departure saturation from drainage to imbibition for gas capillary pressure hysteresis"}, - "BGSTRP": {"type": "block", "description": "Trapped gas critical saturation for gas capillary pressure hysteresis"}, - "BWSHY": {"type": "block", "description": "Departure saturation from drainage to imbibition for water capillary pressure hysteresis"}, - "BWSMA": {"type": "block", "description": "Maximum wetting saturation for water capillary pressure hysteresis"}, - "BMLSC": {"type": "block", "description": "Hydrocarbon molar density"}, - "BMLST": {"type": "block", "description": "Total hydrocarbon molar density"}, - "BMWAT": {"type": "block", "description": "Water molar density"}, - "BROMLS": {"type": "block", "description": "Residual oil moles/ reservoir volume"}, - "BJV": {"type": "block", "description": "In"}, - "BVMF": {"type": "block", "description": "Vapor mole fraction"}, - "BPSAT": {"type": "block", "description": "Saturation Pressures"}, - "BAMF": {"type": "block", "description": "Component aqueous mole fraction"}, - "BXMF": {"type": "block", "description": "Liquid hydrocarbon component mole fraction"}, - "BYMF": {"type": "block", "description": "Vapor hydrocarbon component mole fraction / vapor steam"}, - "BSMF": {"type": "block", "description": "CO2STORE with SOLID option only Solid hydrocarbon component mole fraction"}, - "BSTEN": {"type": "block", "description": "Surface Tension"}, - "BFMISC": {"type": "block", "description": "Miscibility Factor"}, - "BREAC": {"type": "block", "description": "Reaction rate. The reaction number is given as a component index"}, - "BHD": {"type": "block", "description": "Hydraulic head"}, - "BHDF": {"type": "block", "description": "Hydraulic head at fresh water conditions"}, - "BPR_X": {"type": "block", "description": "Pressure interpolated at a defined coordinate"}, - "BHD_X": {"type": "block", "description": "Hydraulic head interpolated at a defined coordinate"}, - "BHDF_X": {"type": "block", "description": "Hydraulic head at fresh water conditions interpolated at a defined coordinate"}, - "BSCN_X": {"type": "block", "description": "Brine concentration interpolated at a defined coordinate"}, - "BCTRA_X": {"type": "block", "description": "Tracer concentration interpolated at a defined coordinate"}, - "LBPR_X": {"type": "block", "description": "Pressure interpolated at a defined coordinate within a local grid"}, - "LBHD_X": {"type": "block", "description": "Hydraulic head interpolated at a defined coordinate within a local grid"}, - "LBHDF_X": {"type": "block", "description": "Hydraulic head at freshwater conditions interpolated at a defined coordinate within a local grid"}, - "LBSCN_X": {"type": "block", "description": "Brine concentration interpolated at a defined coordinate within a local grid"}, - "LBCTRA_X": {"type": "block", "description": "Tracer concentration interpolated at a defined coordinate within a local grid"}, - "BOKRX": {"type": "block", "description": "Oil relative permeability in the X direction"}, - "BOKRY": {"type": "block", "description": "Oil relative permeability in the Y direction"}, - "BOKRZ": {"type": "block", "description": "Oil relative permeability in the Z direction"}, - "BWKRX": {"type": "block", "description": "Water relative permeability in the X direction"}, - "BWKRY": {"type": "block", "description": "Water relative permeability in the Y direction"}, - "BWKRZ": {"type": "block", "description": "Water relative permeability in the Z direction"}, - "BGKRX": {"type": "block", "description": "Gas relative permeability in the X direction"}, - "BGKRY": {"type": "block", "description": "Gas relative permeability in the Y direction"}, - "BGKRZ": {"type": "block", "description": "Gas relative permeability in the Z direction"}, - "BOKRI": {"type": "block", "description": "Oil relative permeability in the I direction"}, - "BOKRJ": {"type": "block", "description": "Oil relative permeability in the J direction"}, - "BOKRK": {"type": "block", "description": "Oil relative permeability in the K direction"}, - "BWKRI": {"type": "block", "description": "Water relative permeability in the I direction"}, - "BWKRJ": {"type": "block", "description": "Water relative permeability in the J direction"}, - "BWKRK": {"type": "block", "description": "Water relative permeability in the K direction"}, - "BGKRI": {"type": "block", "description": "Gas relative permeability in the I direction"}, - "BGKRJ": {"type": "block", "description": "Gas relative permeability in the J direction"}, - "BGKRK": {"type": "block", "description": "Gas relative permeability in the K direction"}, - "BOKRR": {"type": "block", "description": "Oil relative permeability in the R"}, - "BOKRT": {"type": "block", "description": "Oil relative permeability in the T"}, - "BWKRR": {"type": "block", "description": "Water relative permeability in the R"}, - "BWKRT": {"type": "block", "description": "Water relative permeability in the T"}, - "BGKRR": {"type": "block", "description": "Gas relative permeability in the R"}, - "BGKRT": {"type": "block", "description": "Gas relative permeability in the T"}, - "BRPV": {"type": "block", "description": "Pore Volume at Reservoir conditions"}, - "BPORV": {"type": "block", "description": "Cell Pore Volumes at Reference conditions"}, - "BOPV": {"type": "block", "description": "Pore Volume containing Oil"}, - "BWPV": {"type": "block", "description": "Pore Volume containing Water"}, - "BGPV": {"type": "block", "description": "Pore Volume containing Gas"}, - "BHPV": {"type": "block", "description": "Pore Volume containing Hydrocarbon"}, - "BRTM": {"type": "block", "description": "Transmissibility Multiplier associated with rock compaction"}, - "BPERMMOD": {"type": "block", "description": "Transmissibility Multiplier associated with rock compaction"}, - "BPERMMDX": {"type": "block", "description": "Directional Transmissibility Multipliers in the X direction, associated with rock compaction"}, - "BPERMMDY": {"type": "block", "description": "Directional Transmissibility Multipliers in the Y direction, associated with rock compaction"}, - "BPERMMDZ": {"type": "block", "description": "Directional Transmissibility Multipliers in the Z direction, associated with rock compaction"}, - "BPORVMOD": {"type": "block", "description": "Pore Volume Multiplier associated with rock compaction"}, - "BSIGMMOD": {"type": "block", "description": "Dual Porosity Sigma Multiplier associated with rock compaction"}, - "BTCNF": {"type": "block", "description": "Tracer Concentration"}, - "BTCNS": {"type": "block", "description": "Tracer Concentration"}, - "BTCN": {"type": "block", "description": "Tracer Concentration"}, - "BTIPT": {"type": "block", "description": "Tracer In Place"}, - "BTIPF": {"type": "block", "description": "Tracer In Place"}, - "BTIPS": {"type": "block", "description": "Tracer In Place"}, - "BAPI": {"type": "block", "description": "Oil API"}, - "BSCN": {"type": "block", "description": "Salt Cell Concentration"}, - "BSIP": {"type": "block", "description": "Salt In Place"}, - "BEWV_SAL": {"type": "block", "description": "Effective water viscosity due to salt concentration"}, - "BTCNFANI": {"type": "block", "description": "Anion Flowing Concentration"}, - "BTCNFCAT": {"type": "block", "description": "Cation Flowing Concentration"}, - "BTRADCAT": {"type": "block", "description": "Cation Rock Associated Concentration"}, - "BTSADCAT": {"type": "block", "description": "Cation Surfactant Associated Concentration"}, - "BESALSUR": {"type": "block", "description": "Effective Salinity with respect to Surfactant"}, - "BESALPLY": {"type": "block", "description": "Effective Salinity with respect to Polymer"}, - "BTCNFHEA": {"type": "block", "description": "Block Temperature"}, - "BTIPTHEA": {"type": "block", "description": "Difference in Energy in place between current and initial time"}, - "BTCN#": {"type": "block", "description": "Tracer concentration in phase # (1,2,3,...)"}, - "BTIP#": {"type": "block", "description": "Tracer In Place in phase # (1,2,3,...)"}, - "BTADS": {"type": "block", "description": "Tracer Adsorption"}, - "BTDCY": {"type": "block", "description": "Decayed tracer"}, - "BCGC": {"type": "block", "description": "Bulk Coal Gas Concentration"}, - "BCSC": {"type": "block", "description": "Bulk Coal Solvent Concentration"}, - "BTCNFFOA": {"type": "block", "description": "Concentration"}, - "BFOAM": {"type": "block", "description": "Surfactant concentration"}, - "BTCNMFOA": {"type": "block", "description": "Capillary number"}, - "BFOAMCNM": {"type": "block", "description": "Capillary number"}, - "BTIPTFOA": {"type": "block", "description": "In Solution"}, - "BTADSFOA": {"type": "block", "description": "Adsorption"}, - "BTDCYFOA": {"type": "block", "description": "Decayed tracer"}, - "BTMOBFOA": {"type": "block", "description": "Gas mobility factor"}, - "BFOAMMOB": {"type": "block", "description": "Gas mobility factor"}, - "BTHLFFOA": {"type": "block", "description": "Decay Half life"}, - "BGI": {"type": "block", "description": "Block Gi value"}, - "BGIP": {"type": "block", "description": "Gas In Place (liquid+gas phase)"}, - "BGIPG": {"type": "block", "description": "Gas In Place (gas phase)"}, - "BGIPL": {"type": "block", "description": "Gas In Place (liquid phase)"}, - "BCCN": {"type": "block", "description": "Polymer Concentration"}, - "BCIP": {"type": "block", "description": "Polymer In Solution"}, - "BEPVIS": {"type": "block", "description": "Effective polymer solution viscosity"}, - "BVPOLY": {"type": "block", "description": "Effective polymer solution viscosity"}, - "BEMVIS": {"type": "block", "description": "Effective mixture"}, - "BEWV_POL": {"type": "block", "description": "Effective water viscosity"}, - "BCAD": {"type": "block", "description": "Polymer Adsorption concentration"}, - "BCDCS": {"type": "block", "description": "Polymer thermal degradation - total mass degraded in previous timestep"}, - "BCDCR": {"type": "block", "description": "Polymer thermal degradation - total degradation rate"}, - "BCDCP": {"type": "block", "description": "Polymer thermal degradation solution degradation rate"}, - "BCDCA": {"type": "block", "description": "Polymer thermal degradation adsorbed degradation rate"}, - "BCABnnn": {"type": "block", "description": "Adsorbed polymer by highest temperature band at which RRF was calculated"}, - "BFLOW0I": {"type": "block", "description": "Inter-block water flow rate in the positive I direction multiplied by the corresponding shear multiplier"}, - "BFLOW0J": {"type": "block", "description": "Inter-block water flow rate in the positive J direction multiplied by the corresponding shear multiplier"}, - "BFLOW0K": {"type": "block", "description": "Inter-block water flow rate in the positive K direction multiplied by the corresponding shear multiplier"}, - "BVELW0I": {"type": "block", "description": "Water velocity in the positive I direction multiplied by the corresponding shear multiplier"}, - "BVELW0J": {"type": "block", "description": "Water velocity in the positive J direction multiplied by the corresponding shear multiplier"}, - "BVELW0K": {"type": "block", "description": "Water velocity in the positive K direction multiplied by the corresponding shear multiplier"}, - "BPSHLZI": {"type": "block", "description": "Viscosity multiplier due to sheared water flow in the positive I direction"}, - "BPSHLZJ": {"type": "block", "description": "Viscosity multiplier due to sheared water flow in the positive J direction"}, - "BPSHLZK": {"type": "block", "description": "Viscosity multiplier due to sheared water flow in the positive K direction"}, - "BSRTW0I": {"type": "block", "description": "Water shear rate in the positive I direction prior to shear effects"}, - "BSRTW0J": {"type": "block", "description": "Water shear rate in the positive J direction prior to shear effects"}, - "BSRTW0K": {"type": "block", "description": "Water shear rate in the positive K direction prior to shear effects"}, - "BSRTWI": {"type": "block", "description": "Water shear rate in the positive I direction following shear effects"}, - "BSRTWJ": {"type": "block", "description": "Water shear rate in the positive J direction following shear effects"}, - "BSRTWK": {"type": "block", "description": "Water shear rate in the positive K direction following shear effects"}, - "BSHWVISI": {"type": "block", "description": "Shear viscosity of the water/polymer solution due to shear thinning/thickening in the positive I direction"}, - "BSHWVISJ": {"type": "block", "description": "Shear viscosity of the water/polymer solution due to shear thinning/thickening in the positive J direction"}, - "BSHWVISK": {"type": "block", "description": "Shear viscosity of the water/polymer solution due to shear thinning/thickening in the positive K direction"}, - "BNSAT": {"type": "block", "description": "Solvent SATuration"}, - "BNIP": {"type": "block", "description": "Solvent In Place"}, - "BNKR": {"type": "block", "description": "Solvent relative permeability"}, - "BTCNFSUR": {"type": "block", "description": "Concentration"}, - "BSURF": {"type": "block", "description": "Concentration in solution"}, - "BTIPTSUR": {"type": "block", "description": "In Solution"}, - "BTADSUR": {"type": "block", "description": "Adsorption"}, - "BTCASUR": {"type": "block", "description": "Log"}, - "BSURFCNM": {"type": "block", "description": "Log"}, - "BTSTSUR": {"type": "block", "description": "Surface tension"}, - "BSURFST": {"type": "block", "description": "Surface tension"}, - "BEWV_SUR": {"type": "block", "description": "Effective water viscosity due to surfactant concentration"}, - "BESVIS": {"type": "block", "description": "Effective water viscosity due to surfactant concentration"}, - "BTCNFALK": {"type": "block", "description": "Concentration"}, - "BTADSALK": {"type": "block", "description": "Adsorption"}, - "BTSTMALK": {"type": "block", "description": "Surface tension multiplier"}, - "BTSADALK": {"type": "block", "description": "Surfactant adsorption multiplier"}, - "BTPADALK": {"type": "block", "description": "Polymer adsorption multiplier"}, - "BKRGOE": {"type": "block", "description": "Equivalent relative permeability to gas for gas-oil system"}, - "BKRGWE": {"type": "block", "description": "Equivalent relative permeability to gas for gas-water system"}, - "BKRWGE": {"type": "block", "description": "Equivalent relative permeability to water for water-gas system"}, - "BKROWT": {"type": "block", "description": "Opposite saturation direction turning point relative permeability to oil for oil-water system"}, - "BKRWOT": {"type": "block", "description": "Opposite saturation direction turning point relative permeability to water for water-oil system"}, - "BKROGT": {"type": "block", "description": "Opposite saturation direction turning point relative permeability to oil for oil-gas system"}, - "BKRGOT": {"type": "block", "description": "Opposite saturation direction turning point relative permeability to gas for gas-oil system"}, - "BKRGWT": {"type": "block", "description": "Opposite saturation direction turning point relative permeability to gas for gas-water system"}, - "BKRWGT": {"type": "block", "description": "Opposite saturation direction turning point relative permeability to water for water-gas system"}, - "BIFTOW": {"type": "block", "description": "Oil-water interfacial tension"}, - "BIFTWO": {"type": "block", "description": "Water-oil interfacial tension"}, - "BIFTOG": {"type": "block", "description": "Oil-gas interfacial tension"}, - "BIFTGO": {"type": "block", "description": "Gas-oil interfacial tension"}, - "BIFTGW": {"type": "block", "description": "Gas-water interfacial tension"}, - "BIFTWG": {"type": "block", "description": "Water-gas interfacial tension"}, - "BPCOWR": {"type": "block", "description": "Representative oil-water capillary pressure"}, - "BPCWOR": {"type": "block", "description": "Representative water-oil capillary pressure"}, - "BPCOGR": {"type": "block", "description": "Representative oil-gas capillary pressure"}, - "BPCGOR": {"type": "block", "description": "Representative gas-oil capillary pressure"}, - "BPCGWR": {"type": "block", "description": "Representative gas-water capillary pressure"}, - "BPCWGR": {"type": "block", "description": "Representative water-gas capillary pressure"}, - "SOFR": {"type": "well_segment", "description": "Segment Oil Flow Rate"}, - "SOFRF": {"type": "well_segment", "description": "Segment Free Oil Flow Rate"}, - "SOFRS": {"type": "well_segment", "description": "Segment Solution Oil Flow Rate"}, - "SWFR": {"type": "well_segment", "description": "Segment Water Flow Rate"}, - "SGFR": {"type": "well_segment", "description": "Segment Gas Flow Rate"}, - "SGFRF": {"type": "well_segment", "description": "Segment Free Gas Flow Rate"}, - "SGFRS": {"type": "well_segment", "description": "Segment Solution Gas Flow Rate"}, - "SKFR": {"type": "well_segment", "description": "Segment Component Flow Rate"}, - "SCWGFR": {"type": "well_segment", "description": "Segment Component Flow Rate as Wet Gas"}, - "SHFR": {"type": "well_segment", "description": "Segment Enthalpy Flow Rate"}, - "SWCT": {"type": "well_segment", "description": "Segment Water Cut"}, - "SGOR": {"type": "well_segment", "description": "Segment Gas Oil Ratio"}, - "SOGR": {"type": "well_segment", "description": "Segment Oil Gas Ratio"}, - "SWGR": {"type": "well_segment", "description": "Segment Water Gas Ratio"}, - "SPR": {"type": "well_segment", "description": "Segment Pressure"}, - "SPRD": {"type": "well_segment", "description": "Segment Pressure Drop"}, - "SPRDF": {"type": "well_segment", "description": "Segment Pressure Drop component due to Friction"}, - "SPRDH": {"type": "well_segment", "description": "Segment Pressure Drop component due to Hydrostatic head"}, - "SPRDA": {"type": "well_segment", "description": "Segment Pressure drop due to Acceleration head"}, - "SPRDM": {"type": "well_segment", "description": "Segment frictional Pressure Drop Multiplier"}, - "SPPOW": {"type": "well_segment", "description": "Working power of a pull through pump"}, - "SOFV": {"type": "well_segment", "description": "Segment Oil Flow Velocity"}, - "SWFV": {"type": "well_segment", "description": "Segment Water Flow Velocity"}, - "SGFV": {"type": "well_segment", "description": "Segment Gas Flow Velocity"}, - "SOHF": {"type": "well_segment", "description": "Segment Oil Holdup Fraction"}, - "SWHF": {"type": "well_segment", "description": "Segment Water Holdup Fraction"}, - "SGHF": {"type": "well_segment", "description": "Segment Gas Holdup Fraction"}, - "SDENM": {"type": "well_segment", "description": "Segment fluid mixture density"}, - "SOVIS": {"type": "well_segment", "description": "Segment oil viscosity"}, - "SWVIS": {"type": "well_segment", "description": "Segment water viscosity"}, - "SGVIS": {"type": "well_segment", "description": "Segment gas viscosity"}, - "SEMVIS": {"type": "well_segment", "description": "Segment effective mixture viscosity"}, - "SGLPP": {"type": "well_segment", "description": "Segment Gas-Liquid Profile Parameter, C0"}, - "SGLVD": {"type": "well_segment", "description": "Segment Gas-Liquid Drift Velocity, Vd"}, - "SOWPP": {"type": "well_segment", "description": "Segment Oil-Water Profile Parameter, C0"}, - "SOWVD": {"type": "well_segment", "description": "Segment Oil-Water Drift Velocity, Vd"}, - "SOIMR": {"type": "well_segment", "description": "Segment Oil Import Rate"}, - "SGIMR": {"type": "well_segment", "description": "Segment Gas Import Rate"}, - "SWIMR": {"type": "well_segment", "description": "Segment Water Import Rate"}, - "SHIMR": {"type": "well_segment", "description": "Segment Enthalpy Import Rate"}, - "SORMR": {"type": "well_segment", "description": "Segment Oil Removal Rate"}, - "SGRMR": {"type": "well_segment", "description": "Segment Gas Removal Rate"}, - "SWRMR": {"type": "well_segment", "description": "Segment Water Removal Rate"}, - "SHRMR": {"type": "well_segment", "description": "Segment Enthalpy Removal Rate"}, - "SOIMT": {"type": "well_segment", "description": "Segment Oil Import Total"}, - "SGIMT": {"type": "well_segment", "description": "Segment Gas Import Total"}, - "SWIMT": {"type": "well_segment", "description": "Segment Water Import Total"}, - "SHIMT": {"type": "well_segment", "description": "Segment Enthalpy Import Total"}, - "SORMT": {"type": "well_segment", "description": "Segment Oil Removal Total"}, - "SGRMT": {"type": "well_segment", "description": "Segment Gas Removal Total"}, - "SWRMT": {"type": "well_segment", "description": "Segment Water Removal Total"}, - "SHRMT": {"type": "well_segment", "description": "Segment Enthalpy Removal Total"}, - "SAPI": {"type": "well_segment", "description": "Segment API value"}, - "SCFR": {"type": "well_segment", "description": "Segment polymer flow rate"}, - "SCCN": {"type": "well_segment", "description": "Segment polymer concentration"}, - "SSFR": {"type": "well_segment", "description": "Segment brine flow rate"}, - "SSCN": {"type": "well_segment", "description": "Segment brine concentration"}, - "STFR": {"type": "well_segment", "description": "Segment tracer flow rate"}, - "STFC": {"type": "well_segment", "description": "Segment tracer concentration"}, - "SFD": {"type": "well_segment", "description": "Segment diameter for Karst Conduit Calcite Dissolution"}, - "SPSAT": {"type": "well_segment", "description": "Segment Psat"}, - "STEM": {"type": "well_segment", "description": "Segment Temperature"}, - "SENE": {"type": "well_segment", "description": "Segment Energy Density"}, - "SSQU": {"type": "well_segment", "description": "Segment Steam Quality"}, - "SCVPR": {"type": "well_segment", "description": "Segment Calorific Value Production Rate"}, - "SGQ": {"type": "well_segment", "description": "Segment Gas Quality"}, - "SCSA": {"type": "well_segment", "description": "Segment Cross Sectional Area"}, - "SSTR": {"type": "well_segment", "description": "Strength of ICD on segment"}, - "SFOPN": {"type": "well_segment", "description": "Setting of segment"}, - "SALQ": {"type": "well_segment", "description": "Artificial lift quantity for segment"}, - "SRRQR": {"type": "well_segment", "description": "Reach flow at current time"}, - "SRRQT": {"type": "well_segment", "description": "Reach cumulative flow"}, - "SRBQR": {"type": "well_segment", "description": "Branch flow at current time"}, - "SRBQT": {"type": "well_segment", "description": "Branch cumulative flow"}, - "SRTQR": {"type": "well_segment", "description": "River total flow at current time"}, - "SRTQT": {"type": "well_segment", "description": "River total cumulative flow"}, - "SRRFLOW": {"type": "well_segment", "description": "Reach flux through cross-sectional area at current time"}, - "SRRAREA": {"type": "well_segment", "description": "Reach area at current time"}, - "SRRDEPTH": {"type": "well_segment", "description": "Reach depth at current time"}, - "SRREXCH": {"type": "well_segment", "description": "Exchange flux at current time"}, - "SRRFRODE": {"type": "well_segment", "description": "Reach Froude number at current time"}, - "SRRHEAD": {"type": "well_segment", "description": "Reach hydraulic head at current time"}, - "SRTFR": {"type": "well_segment", "description": "Reach tracer flow rate"}, - "SRTFC": {"type": "well_segment", "description": "Reach tracer concentration"}, - "SRSFR": {"type": "well_segment", "description": "Reach brine flow rate through connections"}, - "SRSFC": {"type": "well_segment", "description": "Reach brine concentration"}, - "SU": {"type": "well_segment", "description": "User-defined segment quantity"}, - "AAQR": {"type": "aquifer", "description": "Aquifer influx rate"}, - "ALQR": {"type": "aquifer", "description": "Aquifer influx rate"}, - "AAQT": {"type": "aquifer", "description": "Cumulative aquifer influx"}, - "ALQT": {"type": "aquifer", "description": "Cumulative aquifer influx"}, - "AAQRG": {"type": "aquifer", "description": "Aquifer influx rate"}, - "ALQRG": {"type": "aquifer", "description": "Aquifer influx rate"}, - "AAQTG": {"type": "aquifer", "description": "Cumulative aquifer influx"}, - "ALQTG": {"type": "aquifer", "description": "Cumulative aquifer influx"}, - "AACMR": {"type": "aquifer", "description": "Aquifer component molar influx rate"}, - "AACMT": {"type": "aquifer", "description": "Aquifer component molar influx totals"}, - "AAQP": {"type": "aquifer", "description": "Aquifer pressure"}, - "AAQER": {"type": "aquifer", "description": "Aquifer thermal energy influx rate"}, - "AAQET": {"type": "aquifer", "description": "Cumulative aquifer thermal energy influx"}, - "AAQTEMP": {"type": "aquifer", "description": "Aquifer temperature"}, - "AAQENTH": {"type": "aquifer", "description": "Aquifer molar enthalpy"}, - "AAQTD": {"type": "aquifer", "description": "Aquifer dimensionless time"}, - "AAQPD": {"type": "aquifer", "description": "Aquifer dimensionless pressure"}, - "ANQR": {"type": "aquifer", "description": "Aquifer influx rate"}, - "ANQT": {"type": "aquifer", "description": "Cumulative aquifer influx"}, - "ANQP": {"type": "aquifer", "description": "Aquifer pressure"}, - "CPU": {"type": "misc", "description": "CPU"}, - "DATE": {"type": "misc", "description": "Date"}, - "DAY": {"type": "misc", "description": "Day"}, - "ELAPSED": {"type": "misc", "description": "Elapsed time in seconds"}, - "MLINEARS": {"type": "misc", "description": "Number linear iterations for each timestep"}, - "MONTH": {"type": "misc", "description": "Month"}, - "MSUMLINS": {"type": "misc", "description": "Total number of linear iterations since the start of the run"}, - "MSUMNEWT": {"type": "misc", "description": "Total number of Newton iterations since the start of the run"}, - "NEWTON": {"type": "misc", "description": "Number of Newton iterations used for each timestep"}, - "STEPTYPE": {"type": "misc", "description": "Step type"}, - "TCPU": {"type": "misc", "description": "TCPU"}, - "TCPUDAY": {"type": "misc", "description": "TCPUDAY"}, - "TCPUTS": {"type": "misc", "description": "TCPUTS"}, - "TELAPLIN": {"type": "misc", "description": "TELAPLIN"}, - "TIME": {"type": "misc", "description": "Time"}, - "TIMESTEP": {"type": "misc", "description": "Time step"}, - "TIMESTRY": {"type": "misc", "description": "TIMESTRY"}, - "YEAR": {"type": "misc", "description": "Year"}, - "YEARS": {"type": "misc", "description": "Years"} -} - From bf6a8913ee02ba8f77a81b0dea5a30f176910b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Fri, 28 Jan 2022 13:50:41 +0100 Subject: [PATCH 11/27] Fix code style & linting --- webviz_subsurface/__init__.py | 10 ++++------ .../_abbreviations/reservoir_simulation.py | 3 +-- .../_utils/user_defined_vector_definitions.py | 2 -- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/webviz_subsurface/__init__.py b/webviz_subsurface/__init__.py index 0a4dd6d8d..2b70c74c4 100644 --- a/webviz_subsurface/__init__.py +++ b/webviz_subsurface/__init__.py @@ -1,22 +1,20 @@ import glob -import json import pathlib -from typing import Dict, List, Optional +from typing import Dict, Optional import jsonschema import webviz_config import yaml from pkg_resources import DistributionNotFound, get_distribution +from webviz_subsurface._utils.user_defined_vector_definitions import ( + USER_DEFINED_VECTOR_DEFINITIONS_JSON_SCHEMA, +) from webviz_subsurface._utils.vector_calculator import ( PREDEFINED_EXPRESSIONS_JSON_SCHEMA, ConfigExpressionData, ) -from webviz_subsurface._utils.user_defined_vector_definitions import ( - USER_DEFINED_VECTOR_DEFINITIONS_JSON_SCHEMA, -) - try: __version__ = get_distribution(__name__).version except DistributionNotFound: diff --git a/webviz_subsurface/_abbreviations/reservoir_simulation.py b/webviz_subsurface/_abbreviations/reservoir_simulation.py index ecaf9b003..ef2ee97d4 100644 --- a/webviz_subsurface/_abbreviations/reservoir_simulation.py +++ b/webviz_subsurface/_abbreviations/reservoir_simulation.py @@ -4,8 +4,7 @@ from typing import Dict, Optional, Tuple, cast import pandas as pd - -from webviz_subsurface_components import VectorDefinitions, VectorDefinition +from webviz_subsurface_components import VectorDefinition, VectorDefinitions _DATA_PATH = pathlib.Path(__file__).parent.absolute() / "abbreviation_data" diff --git a/webviz_subsurface/_utils/user_defined_vector_definitions.py b/webviz_subsurface/_utils/user_defined_vector_definitions.py index 255663136..795c3133a 100644 --- a/webviz_subsurface/_utils/user_defined_vector_definitions.py +++ b/webviz_subsurface/_utils/user_defined_vector_definitions.py @@ -1,10 +1,8 @@ import sys - from pathlib import Path from typing import Dict, Optional import yaml - from webviz_subsurface_components import VectorDefinition if sys.version_info >= (3, 8): From e515856e01c4e4231dbd543003d3d980a7ba38cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Fri, 28 Jan 2022 15:01:26 +0100 Subject: [PATCH 12/27] Added changelog Correct changelog - "Added" and "Changed" info Fix typo --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab46403e..a61271da3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#929](https://github.com/equinor/webviz-subsurface/pull/929) - `TornadoWidget` - No longer skipping sensitivities with `SENSNAME="ref"` from tornado bars if there is more than one realization with `SENSNAME="ref"`. - [#932](https://github.com/equinor/webviz-subsurface/pull/932) - `RftPlotter` - Fixed bug related to calculated correlations returning `NaN` if the response variable has constant values. -### Changed +### Added +- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Added configurable user defined vector definitions. -- [#935](https://github.com/equinor/webviz-subsurface/pull/935) - Deprecated plugin `ReservoirSimulationTimeSeries`. This has been replaced by the faster, more flexible and less memory hungry plugin `SimulationTimeSeries`. ### Changed +- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Changed vector annotation from "AVG_" with suffix "R" and "INTVL_" to "PER_DAY_" and "PER_INTVL_". Retrieve `VectorDefinitions` via Python-API for `webviz-subsurface-components`. - [#924](https://github.com/equinor/webviz-subsurface/pull/924) - Improvements to the `ParameterFilter` functionality, e.g information regarding active filters and which relizatons are filtered out, and better handling of multiple ensembles. Improvements to the `ParameterAnalysis` plugin, e.g. added boxplot, fixed table formatting and update of parameters based on selected ensemble. +- [#935](https://github.com/equinor/webviz-subsurface/pull/935) - Deprecated plugin `ReservoirSimulationTimeSeries`. This has been replaced by the faster, more flexible and less memory hungry plugin `SimulationTimeSeries`. ## [0.2.9] - 2022-01-06 From 43890ede82818ba9fd66968b5ceac6f729cfb098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Mon, 31 Jan 2022 10:50:31 +0100 Subject: [PATCH 13/27] Fix minor SettingWithCopyWarning bug - When downloading statistics, .loc gave SettingWithCopyWarning warning. - Remove usage of .loc as chained index is not needed: ["DATE"] instead of [("DATE", "")]. - Make copy as download is not performance critical and statistics df contains less data. --- .../plugins/_simulation_time_series/_callbacks.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index 827b9ded2..e5f6e5a19 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -617,11 +617,11 @@ def _user_download_data( vector_key = vector + "_statistics" if is_per_interval_or_per_day_vector(vector): - vector_statistics_df.loc[ - :, ("DATE", "") - ] = vector_statistics_df.loc[:, ("DATE", "")].apply( - datetime_to_intervalstr, freq=resampling_frequency - ) + # Copy df to prevent SettingWithCopyWarning + vector_statistics_df = vector_statistics_df.copy() + vector_statistics_df["DATE"] = vector_statistics_df[ + "DATE" + ].apply(datetime_to_intervalstr, freq=resampling_frequency) if vector_dataframe_dict.get(vector_key) is None: vector_dataframe_dict[vector_key] = vector_statistics_df else: From 8ab82ff40d9598cf8817f6cc396856422e8b24fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Tue, 1 Feb 2022 15:15:26 +0100 Subject: [PATCH 14/27] Data relative to date - Prototype implementation --- .../_simulation_time_series/_callbacks.py | 184 +++++++++++++++++- .../_simulation_time_series/_layout.py | 25 +++ .../_simulation_time_series/_plugin.py | 1 + ...ed_delta_ensemble_vectors_accessor_impl.py | 27 ++- .../derived_ensemble_vectors_accessor_impl.py | 27 ++- .../types/derived_vectors_accessor.py | 57 ++++++ .../types/provider_set.py | 37 +++- .../utils/datetime_utils.py | 43 ++++ ...derived_ensemble_vectors_accessor_utils.py | 15 +- .../utils/provider_utils.py | 22 +++ 10 files changed, 426 insertions(+), 12 deletions(-) create mode 100644 webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py create mode 100644 webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index e5f6e5a19..ae282e9e6 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -1,5 +1,7 @@ import copy -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, List, Optional, Set, Tuple, Union + +import datetime import dash import pandas as pd @@ -43,7 +45,10 @@ TraceOptions, VisualizationOptions, ) -from .utils.delta_ensemble_utils import create_delta_ensemble_names +from .utils import datetime_utils +from .utils.delta_ensemble_utils import ( + create_delta_ensemble_names, +) from .utils.derived_ensemble_vectors_accessor_utils import ( create_derived_vectors_accessor_dict, ) @@ -52,6 +57,7 @@ is_per_interval_or_per_day_vector, ) from .utils.history_vectors import create_history_vectors_df +from .utils import provider_utils from .utils.provider_set_utils import create_vector_plot_titles_from_provider_set from .utils.trace_line_shape import get_simulation_line_shape from .utils.vector_statistics import create_vectors_statistics_df @@ -113,6 +119,10 @@ def plugin_callbacks( get_uuid(LayoutElements.STATISTICS_FROM_RADIO_ITEMS), "value", ), + Input( + get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), + "value", + ), Input( get_uuid(LayoutElements.GRAPH_DATA_HAS_CHANGED_TRIGGER), "data", @@ -141,6 +151,7 @@ def _update_graph( resampling_frequency_value: str, selected_realizations: List[int], statistics_calculated_from_value: str, + relative_date_value: str, __graph_data_has_changed_trigger: int, delta_ensembles: List[DeltaEnsemble], vector_calculator_expressions: List[ExpressionInfo], @@ -187,6 +198,22 @@ def _update_graph( all_ensemble_names = [option["value"] for option in ensemble_dropdown_options] statistics_from_option = StatisticsFromOptions(statistics_calculated_from_value) + relative_date: Optional[datetime.datetime] = ( + None + if relative_date_value is None + else datetime_utils.from_str(relative_date_value) + ) + + # ************************** + # + # TODO: REMOVE CODE!!!! + + print(relative_date_value) + print(relative_date) + + # + # ************************** + # Prevent update if realization filtering is not affecting pure statistics plot # TODO: Refactor code or create utility for getting trigger ID in a "cleaner" way? ctx = dash.callback_context.triggered @@ -212,6 +239,7 @@ def _update_graph( expressions=selected_expressions, delta_ensembles=delta_ensembles, resampling_frequency=resampling_frequency, + relative_date=relative_date, ) # TODO: How to get metadata for calculated vector? @@ -507,6 +535,7 @@ def _user_download_data( expressions=selected_expressions, delta_ensembles=delta_ensembles, resampling_frequency=resampling_frequency, + relative_date=None, ) # Dict with vector name as key and dataframe data as value @@ -908,6 +937,157 @@ def _update_realization_range(realizations: List[int]) -> Optional[str]: return realizations_filter_text + # @app.callback( + # [ + # Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), + # Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), + # ], + # [ + # Input(get_uuid(LayoutElements.ENSEMBLES_DROPDOWN), "value"), + # Input(get_uuid(LayoutElements.REALIZATIONS_FILTER_SELECTOR), "value"), + # Input( + # get_uuid(LayoutElements.RESAMPLING_FREQUENCY_DROPDOWN), + # "value", + # ), + # ], + # [ + # State( + # get_uuid(LayoutElements.CREATED_DELTA_ENSEMBLES), + # "data", + # ), + # State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), + # State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), + # ], + # ) + # def _update_relative_date_dropdown( + # selected_ensembles: List[str], + # selected_realizations: List[int], + # resampling_frequency_value: str, + # delta_ensembles: List[DeltaEnsemble], + # current_dropdown_options: List[dict], + # current_dropdown_value: Optional[str], + # ) -> Tuple[List[Dict[str, str]], Optional[str]]: + # resampling_frequency = Frequency.from_string_value(resampling_frequency_value) + + # # Create list of all provider for selected ensembles! + # selected_providers: List[EnsembleSummaryProvider] = [] + # delta_ensemble_name_dict = create_delta_ensemble_name_dict(delta_ensembles) + # for ensemble in selected_ensembles: + # if ensemble in input_provider_set.names(): + # selected_providers.append(input_provider_set.provider(ensemble)) + # elif ensemble in delta_ensemble_name_dict.keys(): + # provider_pair = create_delta_ensemble_provider_pair( + # delta_ensemble_name_dict[ensemble], input_provider_set + # ) + # if provider_pair[0] not in selected_providers: + # selected_providers.append(provider_pair[0]) + # if provider_pair[1] not in selected_providers: + # selected_providers.append(provider_pair[1]) + + # # Create intersection of dates from selected ensembles + # dates_intersection: Set[datetime.datetime] = set() + # for provider in selected_providers: + # realizations_query = provider_utils.create_valid_realizations_query( + # selected_realizations, provider + # ) + # _dates = set(provider.dates(resampling_frequency, realizations_query)) + # if dates_intersection == set(): + # dates_intersection = _dates + # else: + # dates_intersection.intersection_update(_dates) + + # # Create dropdown options: + # new_dropdown_options: List[Dict[str, str]] = [ + # { + # "label": datetime_utils.to_str(_date), + # "value": datetime_utils.to_str(_date), + # } + # for _date in sorted(list(dates_intersection)) + # ] + + # # Create valid dropdown value: + # new_dropdown_value = next( + # ( + # elm["value"] + # for elm in new_dropdown_options + # if elm["value"] == current_dropdown_value + # ), + # None, + # ) + + # # Prevent updates if unchanged + # if new_dropdown_options == current_dropdown_options: + # new_dropdown_options = dash.no_update + # if new_dropdown_value == current_dropdown_value: + # new_dropdown_value = dash.no_update + + # return new_dropdown_options, new_dropdown_value + + @app.callback( + [ + Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), + Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), + ], + [ + Input( + get_uuid(LayoutElements.RESAMPLING_FREQUENCY_DROPDOWN), + "value", + ), + ], + [ + State(get_uuid(LayoutElements.REALIZATIONS_FILTER_SELECTOR), "value"), + State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), + State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), + ], + ) + def _update_relative_date_dropdown( + resampling_frequency_value: str, + selected_realizations: List[int], + current_dropdown_options: List[dict], + current_dropdown_value: Optional[str], + ) -> Tuple[List[Dict[str, str]], Optional[str]]: + """This callback updates dropdown based on selected resampling frequency selection + + If dates are not existing for a provider, the data accessor must handle invalid + relative date selection! + """ + resampling_frequency = Frequency.from_string_value(resampling_frequency_value) + + dates_union: Set[datetime.datetime] = set() + for provider in input_provider_set.all_providers(): + realizations_query = provider_utils.create_valid_realizations_query( + selected_realizations, provider + ) + _dates = set(provider.dates(resampling_frequency, realizations_query)) + dates_union.update(_dates) + + # Create dropdown options: + new_dropdown_options: List[Dict[str, str]] = [ + { + "label": datetime_utils.to_str(_date), + "value": datetime_utils.to_str(_date), + } + for _date in sorted(list(dates_union)) + ] + + # Create valid dropdown value: + new_dropdown_value = next( + ( + elm["value"] + for elm in new_dropdown_options + if elm["value"] == current_dropdown_value + ), + None, + ) + + # Prevent updates if unchanged + if new_dropdown_options == current_dropdown_options: + new_dropdown_options = dash.no_update + if new_dropdown_value == current_dropdown_value: + new_dropdown_value = dash.no_update + + return new_dropdown_options, new_dropdown_value + def _create_delta_ensemble_table_column_data( column_name: str, ensemble_names: List[str] diff --git a/webviz_subsurface/plugins/_simulation_time_series/_layout.py b/webviz_subsurface/plugins/_simulation_time_series/_layout.py index 2cf823454..8ce2fb09f 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_layout.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_layout.py @@ -1,5 +1,7 @@ from typing import Callable, List, Optional +import datetime + import dash_bootstrap_components as dbc import webviz_core_components as wcc import webviz_subsurface_components as wsc @@ -17,6 +19,8 @@ VisualizationOptions, ) +from .utils import datetime_utils + # pylint: disable=too-few-public-methods class LayoutElements: @@ -51,6 +55,8 @@ class LayoutElements: "created_delta_ensemble_names_table_column" ) + RELATIVE_DATE_DROPDOWN = "relative_date_dropdown" + VISUALIZATION_RADIO_ITEMS = "visualization_radio_items" PLOT_FANCHART_OPTIONS_CHECKLIST = "plot_fanchart_options_checklist" @@ -86,6 +92,7 @@ def main_layout( disable_resampling_dropdown: bool, selected_resampling_frequency: Frequency, selected_visualization: VisualizationOptions, + selected_ensembles_dates: List[datetime.datetime], selected_vectors: Optional[List[str]] = None, ) -> html.Div: return wcc.FlexBox( @@ -108,6 +115,7 @@ def main_layout( selected_resampling_frequency=selected_resampling_frequency, selected_visualization=selected_visualization, selected_vectors=selected_vectors, + selected_ensembles_dates=selected_ensembles_dates, ), ), ), @@ -152,6 +160,7 @@ def __settings_layout( disable_resampling_dropdown: bool, selected_resampling_frequency: Frequency, selected_visualization: VisualizationOptions, + selected_ensembles_dates: List[datetime.datetime], selected_vectors: Optional[List[str]] = None, ) -> html.Div: return html.Div( @@ -200,6 +209,22 @@ def __settings_layout( ), ], ), + wcc.Selectors( + label="Data relative to date", + open_details=True, + children=wcc.Dropdown( + label="Select Date", + clearable=True, + id=get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), + options=[ + { + "label": datetime_utils.to_str(_date), + "value": datetime_utils.to_str(_date), + } + for _date in sorted(selected_ensembles_dates) + ], + ), + ), wcc.Selectors( label="Ensembles", children=[ diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 60fe50990..0053e4795 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -281,6 +281,7 @@ def layout(self) -> wcc.FlexBox: selected_resampling_frequency=self._sampling, selected_visualization=self._initial_visualization_selection, selected_vectors=self._initial_vectors, + selected_ensembles_dates=self._input_provider_set.all_dates(self._sampling), ) def set_callbacks(self, app: dash.Dash) -> None: diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py index ae11f2cff..4efc1e34a 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py @@ -1,5 +1,7 @@ from typing import List, Optional, Sequence, Tuple +import datetime + import pandas as pd from webviz_subsurface_components import ExpressionInfo @@ -41,6 +43,7 @@ def __init__( vectors: List[str], expressions: Optional[List[ExpressionInfo]] = None, resampling_frequency: Optional[Frequency] = None, + relative_date: Optional[datetime.datetime] = None, ) -> None: if len(provider_pair) != 2: raise ValueError( @@ -100,6 +103,8 @@ def __init__( else None ) + self._relative_date = relative_date + def __create_delta_ensemble_vectors_df( self, vector_names: List[str], @@ -179,8 +184,16 @@ def get_provider_vectors_df( f'Vector data handler for provider "{self._name}" has no provider vectors' ) - return self.__create_delta_ensemble_vectors_df( - self._provider_vectors, self._resampling_frequency, realizations + if not self._relative_date: + return self.__create_delta_ensemble_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations + ) + + return DerivedVectorsAccessor._create_relative_to_date_df( + self.__create_delta_ensemble_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations + ), + self._relative_date, ) def create_per_interval_and_per_day_vectors_df( @@ -243,6 +256,11 @@ def create_per_interval_and_per_day_vectors_df( how="inner", ) + if self._relative_date: + return DerivedVectorsAccessor._create_relative_to_date_df( + per_interval_and_per_day_vectors_df, + self._relative_date, + ) return per_interval_and_per_day_vectors_df def create_calculated_vectors_df( @@ -323,4 +341,9 @@ def __inner_merge_dataframes( make_date_column_datetime_object(delta_ensemble_calculated_vectors_df) + if self._relative_date: + return DerivedVectorsAccessor._create_relative_to_date_df( + delta_ensemble_calculated_vectors_df, + self._relative_date, + ) return delta_ensemble_calculated_vectors_df diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py index 4fbf4efba..5ac8d8b0b 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py @@ -1,5 +1,7 @@ from typing import List, Optional, Sequence +import datetime + import pandas as pd from webviz_subsurface_components import ExpressionInfo @@ -40,6 +42,7 @@ def __init__( vectors: List[str], expressions: Optional[List[ExpressionInfo]] = None, resampling_frequency: Optional[Frequency] = None, + relative_date: Optional[datetime.datetime] = None, ) -> None: # Initialize base class super().__init__(provider.realizations()) @@ -63,6 +66,7 @@ def __init__( self._resampling_frequency = ( resampling_frequency if self._provider.supports_resampling() else None ) + self._relative_date = relative_date def has_provider_vectors(self) -> bool: return len(self._provider_vectors) > 0 @@ -81,8 +85,16 @@ def get_provider_vectors_df( raise ValueError( f'Vector data handler for provider "{self._name}" has no provider vectors' ) - return self._provider.get_vectors_df( - self._provider_vectors, self._resampling_frequency, realizations + if not self._relative_date: + return self._provider.get_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations + ) + + return DerivedVectorsAccessor._create_relative_to_date_df( + self._provider.get_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations + ), + self._relative_date, ) def create_per_interval_and_per_day_vectors_df( @@ -145,6 +157,11 @@ def create_per_interval_and_per_day_vectors_df( how="inner", ) + if self._relative_date: + return DerivedVectorsAccessor._create_relative_to_date_df( + per_interval_and_per_day_vectors_df, + self._relative_date, + ) return per_interval_and_per_day_vectors_df def create_calculated_vectors_df( @@ -184,4 +201,10 @@ def create_calculated_vectors_df( calculated_vector_df, how="inner", ) + + if self._relative_date: + return DerivedVectorsAccessor._create_relative_to_date_df( + calculated_vectors_df, + self._relative_date, + ) return calculated_vectors_df diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py index 267c8f49c..07d8425ec 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py @@ -1,8 +1,12 @@ import abc from typing import List, Optional, Sequence +import datetime + import pandas as pd +from webviz_subsurface._utils.dataframe_utils import make_date_column_datetime_object + class DerivedVectorsAccessor: def __init__(self, accessor_realizations: List[int]) -> None: @@ -56,3 +60,56 @@ def create_valid_realizations_query( for realization in selected_realizations if realization in self._accessor_realizations ] + + @staticmethod + def _create_relative_to_date_df( + df: pd.DataFrame, relative_date: datetime.datetime + ) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE + + TODO: + - OPTIMIZE CODE/ REFACTOR + - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? + """ + + _relative_date_df: pd.DataFrame = ( + df.loc[df["DATE"] == relative_date] + .drop(columns=["DATE"]) + .set_index(["REAL"]) + ) + if _relative_date_df.empty: + return _relative_date_df + + output_df = pd.DataFrame(columns=df.columns) + for __, _df in df.groupby("DATE"): + # TODO: Simplify code within loop? + _date = _df["DATE"] + _date_index = pd.Index(_date) + + _df.drop(columns=["DATE"], inplace=True) + _df.set_index(["REAL"], inplace=True) + + # TODO: What if "REAL" is not matching between _relative_date_df and _df + res = _df.sub(_relative_date_df) # .dropna(axis=0, how="any") + res.reset_index(inplace=True) + res.set_index(_date_index, inplace=True) + res.reset_index(inplace=True) + + output_df = pd.concat([output_df, res], ignore_index=True) + + # TODO: Drop sorting? + output_df.sort_values(["REAL", "DATE"], ignore_index=True, inplace=True) + + make_date_column_datetime_object(output_df) + + return output_df diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py index ed969ff5f..e5b6c9481 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py @@ -1,3 +1,4 @@ +import datetime from pathlib import Path from typing import Dict, ItemsView, List, Optional, Sequence, Set @@ -18,6 +19,7 @@ class ProviderSet: def __init__(self, provider_dict: Dict[str, EnsembleSummaryProvider]) -> None: self._provider_dict = provider_dict.copy() + self._names = list(self._provider_dict.keys()) self._all_vector_names = self._create_union_of_vector_names_from_providers( list(self._provider_dict.values()) ) @@ -90,7 +92,7 @@ def items(self) -> ItemsView[str, EnsembleSummaryProvider]: return self._provider_dict.items() def names(self) -> List[str]: - return list(self._provider_dict.keys()) + return self._names def provider(self, name: str) -> EnsembleSummaryProvider: if name not in self._provider_dict.keys(): @@ -100,6 +102,39 @@ def provider(self, name: str) -> EnsembleSummaryProvider: def all_providers(self) -> List[EnsembleSummaryProvider]: return list(self._provider_dict.values()) + def dates( + self, + names: List[str], + resampling_frequency: Optional[Frequency], + realizations: Optional[Sequence[int]] = None, + ) -> List[datetime.datetime]: + # TODO: Verify method - delete or modify? + for name in names: + if name not in self._names: + raise ValueError( + f'Provider set does not contain provider named "{name}"!' + ) + _dates: Set[datetime.datetime] = set() + for name in names: + _dates.update( + self._provider_dict[name].dates(resampling_frequency, realizations) + ) + output = list(sorted(_dates)) + return output + + def all_dates( + self, + resampling_frequency: Optional[Frequency], + ) -> List[datetime.datetime]: + # TODO: Verify method - delete or modify? + # Consider adding argument: realizations: Optional[Sequence[int]] = None + dates_union: Set[datetime.datetime] = set() + for provider in self.all_providers(): + realizations_query = provider.realizations() # TODO: None? + _dates = set(provider.dates(resampling_frequency, realizations_query)) + dates_union.update(_dates) + return list(sorted(dates_union)) + def all_realizations(self) -> List[int]: """List with the union of realizations among providers""" return self._all_realizations diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py new file mode 100644 index 000000000..b72ac31b6 --- /dev/null +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py @@ -0,0 +1,43 @@ +import datetime + +# DOCS: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior + + +def from_str(date_str: str) -> datetime.datetime: + return datetime.datetime.strptime(date_str, "%Y-%m-%d") + + +def to_str(date: datetime.datetime) -> str: + if ( + date.hour != 0 + and date.minute != 0 + and date.second != 0 + and date.microsecond != 0 + ): + raise ValueError( + f"Invalid date resolution, expected no data for hour, minute, second" + f" or microsecond for {str(date)}" + ) + return date.strftime("%Y-%m-%d") + + +# from dateutil import parser + +# NOTE: ADD "python-dateutil>=2.8.2", to setup.py if so! + +# def from_str(date_str: str) -> datetime.datetime: +# return parser.parse(date_str) + + +# def to_str(date: datetime.datetime) -> str: +# str() +# if ( +# date.hour != 0 +# and date.minute != 0 +# and date.second != 0 +# and date.microsecond != 0 +# ): +# raise ValueError( +# f"Invalid date resolution, expected no data finer than day for {str(date)}" +# ) +# return date.strftime("%m-%d-%y") diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py index c9b529e33..b15156a83 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py @@ -1,5 +1,7 @@ from typing import Dict, List, Optional +import datetime + from webviz_subsurface_components import ExpressionInfo from webviz_subsurface._providers import Frequency @@ -25,6 +27,7 @@ def create_derived_vectors_accessor_dict( expressions: List[ExpressionInfo], delta_ensembles: List[DeltaEnsemble], resampling_frequency: Optional[Frequency], + relative_date: Optional[datetime.datetime], ) -> Dict[str, DerivedVectorsAccessor]: """Create dictionary with ensemble name as key and derived vectors accessor as key. @@ -58,11 +61,12 @@ def create_derived_vectors_accessor_dict( for ensemble in ensembles: if ensemble in provider_names: ensemble_data_accessor_dict[ensemble] = DerivedEnsembleVectorsAccessorImpl( - ensemble, - provider_set.provider(ensemble), - vectors, - expressions, - resampling_frequency, + name=ensemble, + provider=provider_set.provider(ensemble), + vectors=vectors, + expressions=expressions, + resampling_frequency=resampling_frequency, + relative_date=relative_date, ) elif ( ensemble in delta_ensemble_name_dict.keys() @@ -81,6 +85,7 @@ def create_derived_vectors_accessor_dict( vectors=vectors, expressions=expressions, resampling_frequency=resampling_frequency, + relative_date=relative_date, ) return ensemble_data_accessor_dict diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py new file mode 100644 index 000000000..8e01f2574 --- /dev/null +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py @@ -0,0 +1,22 @@ +from typing import List, Optional + +from webviz_subsurface._providers import EnsembleSummaryProvider + + +def create_valid_realizations_query( + selected_realizations: List[int], provider: EnsembleSummaryProvider +) -> Optional[List[int]]: + """Create realizations query for provider based on selected realizations. + + `Returns:` + - None - If all realizations for provider is selected, i.e. the query is non-filtering + - List[int] - List of realization numbers existing for the provider - empty list + is returned if no realizations exist. + """ + if set(provider.realizations()).issubset(set(selected_realizations)): + return None + return [ + realization + for realization in selected_realizations + if realization in provider.realizations() + ] From 492c4cc3204a1df9be345646fa9451d228f32efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Wed, 2 Feb 2022 09:44:27 +0100 Subject: [PATCH 15/27] Minor adjustment and add demo date for testing --- .../_simulation_time_series/_callbacks.py | 8 +++++ .../_simulation_time_series/_layout.py | 31 +++++++++---------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index ae282e9e6..c9c586c87 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -1070,6 +1070,14 @@ def _update_relative_date_dropdown( for _date in sorted(list(dates_union)) ] + # TODO: REMOVE!! Only added to obtain invalid date for testing! + new_dropdown_options.append( + { + "label": datetime_utils.to_str(datetime.datetime(2264, 1, 1)), + "value": datetime_utils.to_str(datetime.datetime(2264, 1, 1)), + } + ) + # Create valid dropdown value: new_dropdown_value = next( ( diff --git a/webviz_subsurface/plugins/_simulation_time_series/_layout.py b/webviz_subsurface/plugins/_simulation_time_series/_layout.py index 8ce2fb09f..a4f7e6f48 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_layout.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_layout.py @@ -201,6 +201,21 @@ def __settings_layout( ], value=selected_resampling_frequency, ), + wcc.Dropdown( + label="Data relative to date", + clearable=True, + id=get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), + options=[ + { + "label": datetime_utils.to_str(_date), + "value": datetime_utils.to_str(_date), + } + for _date in sorted(selected_ensembles_dates) + ], + style={ + "margin-top": "5px", + }, + ), wcc.Label( "NB: Disabled for presampled data", style={"font-style": "italic"} @@ -209,22 +224,6 @@ def __settings_layout( ), ], ), - wcc.Selectors( - label="Data relative to date", - open_details=True, - children=wcc.Dropdown( - label="Select Date", - clearable=True, - id=get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), - options=[ - { - "label": datetime_utils.to_str(_date), - "value": datetime_utils.to_str(_date), - } - for _date in sorted(selected_ensembles_dates) - ], - ), - ), wcc.Selectors( label="Ensembles", children=[ From 1af4219c1b64703bd4c4309c4a905f39787d363d Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Wed, 2 Feb 2022 13:19:50 +0100 Subject: [PATCH 16/27] Next iteration prototype --- .../_simulation_time_series/_callbacks.py | 161 ++++++------------ .../_simulation_time_series/_layout.py | 9 +- .../_simulation_time_series/_plugin.py | 2 +- .../types/provider_set.py | 21 +-- .../utils/provider_utils.py | 22 --- 5 files changed, 56 insertions(+), 159 deletions(-) delete mode 100644 webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index c9c586c87..4c106d360 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -1,5 +1,5 @@ import copy -from typing import Callable, Dict, List, Optional, Set, Tuple, Union +from typing import Callable, Dict, List, Optional, Tuple, Union import datetime @@ -57,7 +57,6 @@ is_per_interval_or_per_day_vector, ) from .utils.history_vectors import create_history_vectors_df -from .utils import provider_utils from .utils.provider_set_utils import create_vector_plot_titles_from_provider_set from .utils.trace_line_shape import get_simulation_line_shape from .utils.vector_statistics import create_vectors_statistics_df @@ -398,6 +397,7 @@ def _update_graph( observations and TraceOptions.OBSERVATIONS in trace_options and not is_only_delta_ensembles + and not relative_date ): for vector in vectors: vector_observations = observations.get(vector) @@ -406,7 +406,7 @@ def _update_graph( # Add history trace # TODO: Improve when new history vector input format is in place - if TraceOptions.HISTORY in trace_options: + if TraceOptions.HISTORY in trace_options and not relative_date: if ( isinstance(figure_builder, VectorSubplotBuilder) and len(selected_input_providers.names()) > 0 @@ -937,92 +937,6 @@ def _update_realization_range(realizations: List[int]) -> Optional[str]: return realizations_filter_text - # @app.callback( - # [ - # Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), - # Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), - # ], - # [ - # Input(get_uuid(LayoutElements.ENSEMBLES_DROPDOWN), "value"), - # Input(get_uuid(LayoutElements.REALIZATIONS_FILTER_SELECTOR), "value"), - # Input( - # get_uuid(LayoutElements.RESAMPLING_FREQUENCY_DROPDOWN), - # "value", - # ), - # ], - # [ - # State( - # get_uuid(LayoutElements.CREATED_DELTA_ENSEMBLES), - # "data", - # ), - # State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), - # State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), - # ], - # ) - # def _update_relative_date_dropdown( - # selected_ensembles: List[str], - # selected_realizations: List[int], - # resampling_frequency_value: str, - # delta_ensembles: List[DeltaEnsemble], - # current_dropdown_options: List[dict], - # current_dropdown_value: Optional[str], - # ) -> Tuple[List[Dict[str, str]], Optional[str]]: - # resampling_frequency = Frequency.from_string_value(resampling_frequency_value) - - # # Create list of all provider for selected ensembles! - # selected_providers: List[EnsembleSummaryProvider] = [] - # delta_ensemble_name_dict = create_delta_ensemble_name_dict(delta_ensembles) - # for ensemble in selected_ensembles: - # if ensemble in input_provider_set.names(): - # selected_providers.append(input_provider_set.provider(ensemble)) - # elif ensemble in delta_ensemble_name_dict.keys(): - # provider_pair = create_delta_ensemble_provider_pair( - # delta_ensemble_name_dict[ensemble], input_provider_set - # ) - # if provider_pair[0] not in selected_providers: - # selected_providers.append(provider_pair[0]) - # if provider_pair[1] not in selected_providers: - # selected_providers.append(provider_pair[1]) - - # # Create intersection of dates from selected ensembles - # dates_intersection: Set[datetime.datetime] = set() - # for provider in selected_providers: - # realizations_query = provider_utils.create_valid_realizations_query( - # selected_realizations, provider - # ) - # _dates = set(provider.dates(resampling_frequency, realizations_query)) - # if dates_intersection == set(): - # dates_intersection = _dates - # else: - # dates_intersection.intersection_update(_dates) - - # # Create dropdown options: - # new_dropdown_options: List[Dict[str, str]] = [ - # { - # "label": datetime_utils.to_str(_date), - # "value": datetime_utils.to_str(_date), - # } - # for _date in sorted(list(dates_intersection)) - # ] - - # # Create valid dropdown value: - # new_dropdown_value = next( - # ( - # elm["value"] - # for elm in new_dropdown_options - # if elm["value"] == current_dropdown_value - # ), - # None, - # ) - - # # Prevent updates if unchanged - # if new_dropdown_options == current_dropdown_options: - # new_dropdown_options = dash.no_update - # if new_dropdown_value == current_dropdown_value: - # new_dropdown_value = dash.no_update - - # return new_dropdown_options, new_dropdown_value - @app.callback( [ Output(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), @@ -1035,16 +949,14 @@ def _update_realization_range(realizations: List[int]) -> Optional[str]: ), ], [ - State(get_uuid(LayoutElements.REALIZATIONS_FILTER_SELECTOR), "value"), State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "options"), State(get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), "value"), ], ) def _update_relative_date_dropdown( resampling_frequency_value: str, - selected_realizations: List[int], - current_dropdown_options: List[dict], - current_dropdown_value: Optional[str], + current_relative_date_options: List[dict], + current_relative_date_value: Optional[str], ) -> Tuple[List[Dict[str, str]], Optional[str]]: """This callback updates dropdown based on selected resampling frequency selection @@ -1052,49 +964,74 @@ def _update_relative_date_dropdown( relative date selection! """ resampling_frequency = Frequency.from_string_value(resampling_frequency_value) - - dates_union: Set[datetime.datetime] = set() - for provider in input_provider_set.all_providers(): - realizations_query = provider_utils.create_valid_realizations_query( - selected_realizations, provider - ) - _dates = set(provider.dates(resampling_frequency, realizations_query)) - dates_union.update(_dates) + dates_union = input_provider_set.all_dates(resampling_frequency) # Create dropdown options: - new_dropdown_options: List[Dict[str, str]] = [ + new_relative_date_options: List[Dict[str, str]] = [ { "label": datetime_utils.to_str(_date), "value": datetime_utils.to_str(_date), } - for _date in sorted(list(dates_union)) + for _date in dates_union ] + # ******************************************************************** # TODO: REMOVE!! Only added to obtain invalid date for testing! - new_dropdown_options.append( + new_relative_date_options.append( { "label": datetime_utils.to_str(datetime.datetime(2264, 1, 1)), "value": datetime_utils.to_str(datetime.datetime(2264, 1, 1)), } ) + # ******************************************************************** # Create valid dropdown value: - new_dropdown_value = next( + new_relative_date_value = next( ( elm["value"] - for elm in new_dropdown_options - if elm["value"] == current_dropdown_value + for elm in new_relative_date_options + if elm["value"] == current_relative_date_value ), None, ) # Prevent updates if unchanged - if new_dropdown_options == current_dropdown_options: - new_dropdown_options = dash.no_update - if new_dropdown_value == current_dropdown_value: - new_dropdown_value = dash.no_update + if new_relative_date_options == current_relative_date_options: + new_relative_date_options = dash.no_update + if new_relative_date_value == current_relative_date_value: + new_relative_date_value = dash.no_update + + return new_relative_date_options, new_relative_date_value + + @app.callback( + [ + Output( + get_uuid(LayoutElements.PLOT_TRACE_OPTIONS_CHECKLIST), + "style", + ), + ], + [ + Input( + get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), + "value", + ) + ], + ) + def _update_trace_options_layout( + relative_date_value: str, + ) -> List[dict]: + """Hide trace options (History and Observation) when relative date is selected""" + + # Convert to Optional[datetime.datime] + relative_date: Optional[datetime.datetime] = ( + None + if relative_date_value is None + else datetime_utils.from_str(relative_date_value) + ) - return new_dropdown_options, new_dropdown_value + if relative_date: + return [{"display": "none"}] + return [{"display": "block"}] def _create_delta_ensemble_table_column_data( diff --git a/webviz_subsurface/plugins/_simulation_time_series/_layout.py b/webviz_subsurface/plugins/_simulation_time_series/_layout.py index a4f7e6f48..07f84d953 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_layout.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_layout.py @@ -92,7 +92,7 @@ def main_layout( disable_resampling_dropdown: bool, selected_resampling_frequency: Frequency, selected_visualization: VisualizationOptions, - selected_ensembles_dates: List[datetime.datetime], + ensembles_dates: List[datetime.datetime], selected_vectors: Optional[List[str]] = None, ) -> html.Div: return wcc.FlexBox( @@ -115,7 +115,7 @@ def main_layout( selected_resampling_frequency=selected_resampling_frequency, selected_visualization=selected_visualization, selected_vectors=selected_vectors, - selected_ensembles_dates=selected_ensembles_dates, + ensembles_dates=ensembles_dates, ), ), ), @@ -160,7 +160,7 @@ def __settings_layout( disable_resampling_dropdown: bool, selected_resampling_frequency: Frequency, selected_visualization: VisualizationOptions, - selected_ensembles_dates: List[datetime.datetime], + ensembles_dates: List[datetime.datetime], selected_vectors: Optional[List[str]] = None, ) -> html.Div: return html.Div( @@ -210,7 +210,7 @@ def __settings_layout( "label": datetime_utils.to_str(_date), "value": datetime_utils.to_str(_date), } - for _date in sorted(selected_ensembles_dates) + for _date in sorted(ensembles_dates) ], style={ "margin-top": "5px", @@ -483,6 +483,7 @@ def __plot_options_layout( children=[ wcc.Checklist( id=get_uuid(LayoutElements.PLOT_TRACE_OPTIONS_CHECKLIST), + style={"display": "block"}, options=[ {"label": "History", "value": TraceOptions.HISTORY.value}, { diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 0053e4795..142a7ebd6 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -281,7 +281,7 @@ def layout(self) -> wcc.FlexBox: selected_resampling_frequency=self._sampling, selected_visualization=self._initial_visualization_selection, selected_vectors=self._initial_vectors, - selected_ensembles_dates=self._input_provider_set.all_dates(self._sampling), + ensembles_dates=self._input_provider_set.all_dates(self._sampling), ) def set_callbacks(self, app: dash.Dash) -> None: diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py index e5b6c9481..95d60cdbb 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py @@ -102,30 +102,11 @@ def provider(self, name: str) -> EnsembleSummaryProvider: def all_providers(self) -> List[EnsembleSummaryProvider]: return list(self._provider_dict.values()) - def dates( - self, - names: List[str], - resampling_frequency: Optional[Frequency], - realizations: Optional[Sequence[int]] = None, - ) -> List[datetime.datetime]: - # TODO: Verify method - delete or modify? - for name in names: - if name not in self._names: - raise ValueError( - f'Provider set does not contain provider named "{name}"!' - ) - _dates: Set[datetime.datetime] = set() - for name in names: - _dates.update( - self._provider_dict[name].dates(resampling_frequency, realizations) - ) - output = list(sorted(_dates)) - return output - def all_dates( self, resampling_frequency: Optional[Frequency], ) -> List[datetime.datetime]: + """List with the union of dates among providers""" # TODO: Verify method - delete or modify? # Consider adding argument: realizations: Optional[Sequence[int]] = None dates_union: Set[datetime.datetime] = set() diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py deleted file mode 100644 index 8e01f2574..000000000 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/provider_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List, Optional - -from webviz_subsurface._providers import EnsembleSummaryProvider - - -def create_valid_realizations_query( - selected_realizations: List[int], provider: EnsembleSummaryProvider -) -> Optional[List[int]]: - """Create realizations query for provider based on selected realizations. - - `Returns:` - - None - If all realizations for provider is selected, i.e. the query is non-filtering - - List[int] - List of realization numbers existing for the provider - empty list - is returned if no realizations exist. - """ - if set(provider.realizations()).issubset(set(selected_realizations)): - return None - return [ - realization - for realization in selected_realizations - if realization in provider.realizations() - ] From 3c5f531c7306bf43c52d0707d16476aa9a6eb531 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 3 Feb 2022 15:24:24 +0100 Subject: [PATCH 17/27] Next iteration Testing improved implementation of relative to date calculation --- .../_simulation_time_series/_callbacks.py | 22 +++---- .../types/derived_vectors_accessor.py | 58 +++++++++++++++++-- .../types/provider_set.py | 3 +- .../utils/vector_statistics.py | 16 +++++ 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index 4c106d360..35148c03c 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -318,21 +318,21 @@ def _update_graph( # Retrive vectors data from accessor vectors_df_list: List[pd.DataFrame] = [] if accessor.has_provider_vectors(): - vectors_df_list.append( - accessor.get_provider_vectors_df(realizations=realizations_query) - ) + _df = accessor.get_provider_vectors_df(realizations=realizations_query) + if _df.shape[0] > 0: + vectors_df_list.append(_df) if accessor.has_per_interval_and_per_day_vectors(): - vectors_df_list.append( - accessor.create_per_interval_and_per_day_vectors_df( - realizations=realizations_query - ) + _df = accessor.create_per_interval_and_per_day_vectors_df( + realizations=realizations_query ) + if _df.shape[0] > 0: + vectors_df_list.append(_df) if accessor.has_vector_calculator_expressions(): - vectors_df_list.append( - accessor.create_calculated_vectors_df( - realizations=realizations_query - ) + _df = accessor.create_calculated_vectors_df( + realizations=realizations_query ) + if _df.shape[0] > 0: + vectors_df_list.append(_df) for vectors_df in vectors_df_list: if visualization == VisualizationOptions.REALIZATIONS: diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py index 07d8425ec..fdb5c3888 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py @@ -62,7 +62,7 @@ def create_valid_realizations_query( ] @staticmethod - def _create_relative_to_date_df( + def _create_relative_to_date_df_2( df: pd.DataFrame, relative_date: datetime.datetime ) -> pd.DataFrame: """ @@ -81,16 +81,16 @@ def _create_relative_to_date_df( - OPTIMIZE CODE/ REFACTOR - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? """ - + output_df = pd.DataFrame(columns=df.columns) _relative_date_df: pd.DataFrame = ( df.loc[df["DATE"] == relative_date] .drop(columns=["DATE"]) .set_index(["REAL"]) ) if _relative_date_df.empty: - return _relative_date_df + # TODO: Return empty dataframe with columns and no rows or input df? + return output_df - output_df = pd.DataFrame(columns=df.columns) for __, _df in df.groupby("DATE"): # TODO: Simplify code within loop? _date = _df["DATE"] @@ -113,3 +113,53 @@ def _create_relative_to_date_df( make_date_column_datetime_object(output_df) return output_df + + @staticmethod + def _create_relative_to_date_df( + df: pd.DataFrame, relative_date: datetime.datetime + ) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE + - This function assumes equal reals for each datetime - df.groupby("REAL") and .loc[_relative_date_df["REAL"] == _real] + needs equal realizations + + TODO: + - OPTIMIZE CODE/ REFACTOR + - Possible to perform calc inplace? + """ + output_df = pd.DataFrame(columns=df.columns) + _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( + columns=["DATE"] + ) + if _relative_date_df.empty: + # TODO: Return empty dataframe with columns and no rows or input df? + return output_df + + if set(_relative_date_df["REAL"]) != set(df["REAL"]): + return output_df + # raise ValueError(f"Missing realizations for relative date {relative_date}!") + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + for _real, _df in df.groupby("REAL"): + # TODO: Verify loc[_relative_date_df["REAL"] == _real] and iloc[0] + _relative_date_data = ( + _relative_date_df.loc[_relative_date_df["REAL"] == _real] + .drop(columns=["REAL"]) + .iloc[0] + ) + _df[vectors] = _df[vectors].sub(_relative_date_data, axis=1) + output_df = pd.concat([output_df, _df], ignore_index=True) + + make_date_column_datetime_object(output_df) + + return output_df diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py index 95d60cdbb..01c1a74a2 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py @@ -111,8 +111,7 @@ def all_dates( # Consider adding argument: realizations: Optional[Sequence[int]] = None dates_union: Set[datetime.datetime] = set() for provider in self.all_providers(): - realizations_query = provider.realizations() # TODO: None? - _dates = set(provider.dates(resampling_frequency, realizations_query)) + _dates = set(provider.dates(resampling_frequency, None)) dates_union.update(_dates) return list(sorted(dates_union)) diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py b/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py index dbe17ef5f..15d29a3b2 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py @@ -34,6 +34,22 @@ def create_vectors_statistics_df(vectors_df: pd.DataFrame) -> pd.DataFrame: (set(columns_list) ^ set(["DATE", "REAL"])), key=columns_list.index ) + # If no rows of data: + if vectors_df.shape[0] <= 0: + columns_tuples = [("DATE", "")] + for vector in vector_names: + columns_tuples.extend( + [ + (vector, StatisticsOptions.MEAN), + (vector, StatisticsOptions.MIN), + (vector, StatisticsOptions.MAX), + (vector, StatisticsOptions.P10), + (vector, StatisticsOptions.P90), + (vector, StatisticsOptions.P50), + ] + ) + return pd.DataFrame(columns=pd.MultiIndex.from_tuples(columns_tuples)) + # Invert p10 and p90 due to oil industry convention. def p10(x: List[float]) -> np.floating: return np.nanpercentile(x, q=90) From c58b908a9612bc180719758e9b8b281a1cd6021c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Tue, 8 Feb 2022 11:22:02 +0100 Subject: [PATCH 18/27] Furhter prototype implementation for testing - Improved calculation methods - Created new implementations of calculation, and testing with timers. - Moved methods into utils-folder --- .../test_derived_vector_accessor.py | 4 +- .../test_utils/test_dataframe_utils.py | 102 ++++ .../_simulation_time_series/_callbacks.py | 37 +- .../_simulation_time_series/_layout.py | 4 +- ...ed_delta_ensemble_vectors_accessor_impl.py | 10 +- .../derived_ensemble_vectors_accessor_impl.py | 10 +- .../types/derived_vectors_accessor.py | 107 ---- .../utils/dataframe_utils.py | 553 ++++++++++++++++++ ...derived_ensemble_vectors_accessor_utils.py | 3 +- 9 files changed, 688 insertions(+), 142 deletions(-) create mode 100644 tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py create mode 100644 webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_vector_accessor.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_vector_accessor.py index 0921e3a2d..90f0cc851 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_vector_accessor.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_types/test_derived_vector_accessor.py @@ -17,7 +17,7 @@ class DerivedVectorsAccessorMock(DerivedVectorsAccessor): def has_provider_vectors(self) -> bool: raise NotImplementedError("Method not implemented for mock!") - def has_interval_and_average_vectors(self) -> bool: + def has_per_interval_and_per_day_vectors(self) -> bool: raise NotImplementedError("Method not implemented for mock!") def has_vector_calculator_expressions(self) -> bool: @@ -28,7 +28,7 @@ def get_provider_vectors_df( ) -> pd.DataFrame: raise NotImplementedError("Method not implemented for mock!") - def create_interval_and_average_vectors_df( + def create_per_interval_and_per_day_vectors_df( self, realizations: Optional[Sequence[int]] = None ) -> pd.DataFrame: raise NotImplementedError("Method not implemented for mock!") diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py new file mode 100644 index 000000000..1e6125d14 --- /dev/null +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py @@ -0,0 +1,102 @@ +import datetime + +import pandas as pd + +from pandas._testing import assert_frame_equal + +from webviz_subsurface.plugins._simulation_time_series.utils.dataframe_utils import ( + create_relative_to_date_df_group_by_real, + create_relative_to_date_df_group_by_real_2, +) + + +def test_create_relative_to_date_df_consistent_realization_for_all_dates() -> None: + # All dates are represented across all realizations + input_df = pd.DataFrame( + columns=["DATE", "REAL", "A", "B"], + data=[ + [datetime.datetime(2263, 1, 1), 1, 10.0, 130.0], + [datetime.datetime(2263, 2, 1), 1, 45.0, 135.0], + [datetime.datetime(2263, 3, 1), 1, 50.0, 140.0], + [datetime.datetime(2263, 4, 1), 1, 55.0, 145.0], + [datetime.datetime(2263, 1, 1), 2, 11.0, 150.0], + [datetime.datetime(2263, 2, 1), 2, 65.0, 155.0], + [datetime.datetime(2263, 3, 1), 2, 70.0, 160.0], + [datetime.datetime(2263, 4, 1), 2, 75.0, 165.0], + [datetime.datetime(2263, 1, 1), 3, 12.0, 170.0], + [datetime.datetime(2263, 2, 1), 3, 85.0, 175.0], + [datetime.datetime(2263, 3, 1), 3, 90.0, 180.0], + [datetime.datetime(2263, 4, 1), 3, 95.0, 185.0], + ], + ) + expected_df = pd.DataFrame( + columns=["DATE", "REAL", "A", "B"], + data=[ + [datetime.datetime(2263, 1, 1), 1, 0.0, 0.0], + [datetime.datetime(2263, 2, 1), 1, 35.0, 5.0], + [datetime.datetime(2263, 3, 1), 1, 40.0, 10.0], + [datetime.datetime(2263, 4, 1), 1, 45.0, 15.0], + [datetime.datetime(2263, 1, 1), 2, 0.0, 0.0], + [datetime.datetime(2263, 2, 1), 2, 54.0, 5.0], + [datetime.datetime(2263, 3, 1), 2, 59.0, 10.0], + [datetime.datetime(2263, 4, 1), 2, 64.0, 15.0], + [datetime.datetime(2263, 1, 1), 3, 0.0, 0.0], + [datetime.datetime(2263, 2, 1), 3, 73.0, 5.0], + [datetime.datetime(2263, 3, 1), 3, 78.0, 10.0], + [datetime.datetime(2263, 4, 1), 3, 83.0, 15.0], + ], + ) + + first_method = create_relative_to_date_df_group_by_real( + input_df, datetime.datetime(2263, 1, 1) + ) + second_method = create_relative_to_date_df_group_by_real_2( + input_df, datetime.datetime(2263, 1, 1) + ) + + assert_frame_equal(first_method, expected_df) + assert_frame_equal(second_method, expected_df) + assert False + + +def test_create_relative_to_date_df_relative_date_missing_realization() -> None: + # Missing datetime.datetime(2263, 1, 1) for real = 3! + input_df = pd.DataFrame( + columns=["DATE", "REAL", "A", "B"], + data=[ + [datetime.datetime(2263, 1, 1), 1, 10.0, 130.0], + [datetime.datetime(2263, 2, 1), 1, 45.0, 135.0], + [datetime.datetime(2263, 3, 1), 1, 50.0, 140.0], + [datetime.datetime(2263, 4, 1), 1, 55.0, 145.0], + [datetime.datetime(2263, 1, 1), 2, 11.0, 150.0], + [datetime.datetime(2263, 2, 1), 2, 65.0, 155.0], + [datetime.datetime(2263, 3, 1), 2, 70.0, 160.0], + [datetime.datetime(2263, 4, 1), 2, 75.0, 165.0], + [datetime.datetime(2263, 2, 1), 3, 85.0, 175.0], + [datetime.datetime(2263, 3, 1), 3, 90.0, 180.0], + [datetime.datetime(2263, 4, 1), 3, 95.0, 185.0], + ], + ) + expected_df = pd.DataFrame( + columns=["DATE", "REAL", "A", "B"], + data=[ + [datetime.datetime(2263, 1, 1), 1, 0.0, 0.0], + [datetime.datetime(2263, 2, 1), 1, 35.0, 5.0], + [datetime.datetime(2263, 3, 1), 1, 40.0, 10.0], + [datetime.datetime(2263, 4, 1), 1, 45.0, 15.0], + [datetime.datetime(2263, 1, 1), 2, 0.0, 0.0], + [datetime.datetime(2263, 2, 1), 2, 54.0, 5.0], + [datetime.datetime(2263, 3, 1), 2, 59.0, 10.0], + [datetime.datetime(2263, 4, 1), 2, 64.0, 15.0], + ], + ) + + first_method = create_relative_to_date_df_group_by_real( + input_df, datetime.datetime(2263, 1, 1) + ) + second_method = create_relative_to_date_df_group_by_real_2( + input_df, datetime.datetime(2263, 1, 1) + ) + assert_frame_equal(first_method, expected_df) + assert_frame_equal(second_method, expected_df) + assert False diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index 35148c03c..d96cfe353 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -1,7 +1,6 @@ import copy -from typing import Callable, Dict, List, Optional, Tuple, Union - import datetime +from typing import Callable, Dict, List, Optional, Tuple, Union import dash import pandas as pd @@ -46,9 +45,7 @@ VisualizationOptions, ) from .utils import datetime_utils -from .utils.delta_ensemble_utils import ( - create_delta_ensemble_names, -) +from .utils.delta_ensemble_utils import create_delta_ensemble_names from .utils.derived_ensemble_vectors_accessor_utils import ( create_derived_vectors_accessor_dict, ) @@ -207,8 +204,8 @@ def _update_graph( # # TODO: REMOVE CODE!!!! - print(relative_date_value) - print(relative_date) + # print(relative_date_value) + # print(relative_date) # # ************************** @@ -318,23 +315,27 @@ def _update_graph( # Retrive vectors data from accessor vectors_df_list: List[pd.DataFrame] = [] if accessor.has_provider_vectors(): - _df = accessor.get_provider_vectors_df(realizations=realizations_query) - if _df.shape[0] > 0: - vectors_df_list.append(_df) + vectors_df_list.append( + accessor.get_provider_vectors_df(realizations=realizations_query) + ) if accessor.has_per_interval_and_per_day_vectors(): - _df = accessor.create_per_interval_and_per_day_vectors_df( - realizations=realizations_query + vectors_df_list.append( + accessor.create_per_interval_and_per_day_vectors_df( + realizations=realizations_query + ) ) - if _df.shape[0] > 0: - vectors_df_list.append(_df) if accessor.has_vector_calculator_expressions(): - _df = accessor.create_calculated_vectors_df( - realizations=realizations_query + vectors_df_list.append( + accessor.create_calculated_vectors_df( + realizations=realizations_query + ) ) - if _df.shape[0] > 0: - vectors_df_list.append(_df) for vectors_df in vectors_df_list: + # Ensure rows of data + if vectors_df.shape[0] <= 0: + continue + if visualization == VisualizationOptions.REALIZATIONS: # Show selected realizations - only filter df if realizations filter # query is not performed diff --git a/webviz_subsurface/plugins/_simulation_time_series/_layout.py b/webviz_subsurface/plugins/_simulation_time_series/_layout.py index 07f84d953..36b009495 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_layout.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_layout.py @@ -1,6 +1,5 @@ -from typing import Callable, List, Optional - import datetime +from typing import Callable, List, Optional import dash_bootstrap_components as dbc import webviz_core_components as wcc @@ -18,7 +17,6 @@ TraceOptions, VisualizationOptions, ) - from .utils import datetime_utils diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py index 4efc1e34a..f097cb534 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py @@ -1,6 +1,5 @@ -from typing import List, Optional, Sequence, Tuple - import datetime +from typing import List, Optional, Sequence, Tuple import pandas as pd from webviz_subsurface_components import ExpressionInfo @@ -12,6 +11,7 @@ get_selected_expressions, ) +from ..utils import dataframe_utils from ..utils.from_timeseries_cumulatives import ( calculate_from_resampled_cumulative_vectors_df, get_cumulative_vector_name, @@ -189,7 +189,7 @@ def get_provider_vectors_df( self._provider_vectors, self._resampling_frequency, realizations ) - return DerivedVectorsAccessor._create_relative_to_date_df( + return dataframe_utils.create_relative_to_date_df( self.__create_delta_ensemble_vectors_df( self._provider_vectors, self._resampling_frequency, realizations ), @@ -257,7 +257,7 @@ def create_per_interval_and_per_day_vectors_df( ) if self._relative_date: - return DerivedVectorsAccessor._create_relative_to_date_df( + return dataframe_utils.create_relative_to_date_df( per_interval_and_per_day_vectors_df, self._relative_date, ) @@ -342,7 +342,7 @@ def __inner_merge_dataframes( make_date_column_datetime_object(delta_ensemble_calculated_vectors_df) if self._relative_date: - return DerivedVectorsAccessor._create_relative_to_date_df( + return dataframe_utils.create_relative_to_date_df( delta_ensemble_calculated_vectors_df, self._relative_date, ) diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py index 5ac8d8b0b..9e22385eb 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py @@ -1,6 +1,5 @@ -from typing import List, Optional, Sequence - import datetime +from typing import List, Optional, Sequence import pandas as pd from webviz_subsurface_components import ExpressionInfo @@ -11,6 +10,7 @@ get_selected_expressions, ) +from ..utils import dataframe_utils from ..utils.from_timeseries_cumulatives import ( calculate_from_resampled_cumulative_vectors_df, get_cumulative_vector_name, @@ -90,7 +90,7 @@ def get_provider_vectors_df( self._provider_vectors, self._resampling_frequency, realizations ) - return DerivedVectorsAccessor._create_relative_to_date_df( + return dataframe_utils.create_relative_to_date_df( self._provider.get_vectors_df( self._provider_vectors, self._resampling_frequency, realizations ), @@ -158,7 +158,7 @@ def create_per_interval_and_per_day_vectors_df( ) if self._relative_date: - return DerivedVectorsAccessor._create_relative_to_date_df( + return dataframe_utils.create_relative_to_date_df( per_interval_and_per_day_vectors_df, self._relative_date, ) @@ -203,7 +203,7 @@ def create_calculated_vectors_df( ) if self._relative_date: - return DerivedVectorsAccessor._create_relative_to_date_df( + return dataframe_utils.create_relative_to_date_df( calculated_vectors_df, self._relative_date, ) diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py index fdb5c3888..267c8f49c 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_vectors_accessor.py @@ -1,12 +1,8 @@ import abc from typing import List, Optional, Sequence -import datetime - import pandas as pd -from webviz_subsurface._utils.dataframe_utils import make_date_column_datetime_object - class DerivedVectorsAccessor: def __init__(self, accessor_realizations: List[int]) -> None: @@ -60,106 +56,3 @@ def create_valid_realizations_query( for realization in selected_realizations if realization in self._accessor_realizations ] - - @staticmethod - def _create_relative_to_date_df_2( - df: pd.DataFrame, relative_date: datetime.datetime - ) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE - - TODO: - - OPTIMIZE CODE/ REFACTOR - - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? - """ - output_df = pd.DataFrame(columns=df.columns) - _relative_date_df: pd.DataFrame = ( - df.loc[df["DATE"] == relative_date] - .drop(columns=["DATE"]) - .set_index(["REAL"]) - ) - if _relative_date_df.empty: - # TODO: Return empty dataframe with columns and no rows or input df? - return output_df - - for __, _df in df.groupby("DATE"): - # TODO: Simplify code within loop? - _date = _df["DATE"] - _date_index = pd.Index(_date) - - _df.drop(columns=["DATE"], inplace=True) - _df.set_index(["REAL"], inplace=True) - - # TODO: What if "REAL" is not matching between _relative_date_df and _df - res = _df.sub(_relative_date_df) # .dropna(axis=0, how="any") - res.reset_index(inplace=True) - res.set_index(_date_index, inplace=True) - res.reset_index(inplace=True) - - output_df = pd.concat([output_df, res], ignore_index=True) - - # TODO: Drop sorting? - output_df.sort_values(["REAL", "DATE"], ignore_index=True, inplace=True) - - make_date_column_datetime_object(output_df) - - return output_df - - @staticmethod - def _create_relative_to_date_df( - df: pd.DataFrame, relative_date: datetime.datetime - ) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: - - THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE - - This function assumes equal reals for each datetime - df.groupby("REAL") and .loc[_relative_date_df["REAL"] == _real] - needs equal realizations - - TODO: - - OPTIMIZE CODE/ REFACTOR - - Possible to perform calc inplace? - """ - output_df = pd.DataFrame(columns=df.columns) - _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( - columns=["DATE"] - ) - if _relative_date_df.empty: - # TODO: Return empty dataframe with columns and no rows or input df? - return output_df - - if set(_relative_date_df["REAL"]) != set(df["REAL"]): - return output_df - # raise ValueError(f"Missing realizations for relative date {relative_date}!") - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - for _real, _df in df.groupby("REAL"): - # TODO: Verify loc[_relative_date_df["REAL"] == _real] and iloc[0] - _relative_date_data = ( - _relative_date_df.loc[_relative_date_df["REAL"] == _real] - .drop(columns=["REAL"]) - .iloc[0] - ) - _df[vectors] = _df[vectors].sub(_relative_date_data, axis=1) - output_df = pd.concat([output_df, _df], ignore_index=True) - - make_date_column_datetime_object(output_df) - - return output_df diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py new file mode 100644 index 000000000..cbeaf1309 --- /dev/null +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py @@ -0,0 +1,553 @@ +import datetime + +import numpy as np +import pandas as pd + +from webviz_subsurface._utils.dataframe_utils import ( + assert_date_column_is_datetime_object, + make_date_column_datetime_object, +) +from webviz_subsurface._utils.perf_timer import PerfTimer + +# ***************************************** +# +# TODO: Verify which function is best! +# +# NOTE: Testing with timer scores create_relative_to_date_df_2 as winner! +# +# ***************************************** + + +def create_relative_to_date_df( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + df_copy = df.copy() + df_copy_2 = df.copy() + print("****************************************") + make_relative_to_date_df(df_copy, relative_date) + make_relative_to_date_df_2(df_copy_2, relative_date) + create_relative_to_date_df_group_by_real(df, relative_date) + create_relative_to_date_df_group_by_real_2(df, relative_date) + create_relative_to_date_df_group_by_real_4(df, relative_date) + # create_relative_to_date_df_group_by_date(df, relative_date) + # create_relative_to_date_df_group_by_date_2(df, relative_date) + return create_relative_to_date_df_group_by_real_3(df, relative_date) + + +def create_relative_to_date_df_group_by_real_2( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + `Output:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - This function iterates over realization group in input df + - For-loop makes it possible to get realization not present in _relative_date_df + - When realization is not present in _relative_date_df, the realization is excluded + from output df. + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + # Columns of correct dtype + _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} + output_df = pd.DataFrame(_columns) + + _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( + columns=["DATE"] + ) + if _relative_date_df.empty: + # Dataframe with columns, but no rows + return output_df + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + # NOTE: This for-loop makes it possible to get real not represented in _relative_date_df! + for _real, _df in df.groupby("REAL"): + # __ = timer.lap_ms() + _relative_date_data = _relative_date_df.loc[ + _relative_date_df["REAL"] == _real + ].drop(columns=["REAL"]) + # _relative_date_date_lap = timer.lap_ms() + + # If realization does not exist in _relative_date_df + if _relative_date_data.empty: + continue + + _df[vectors] = _df[vectors].sub(_relative_date_data.iloc[0], axis=1) + # _sub_lap = timer.lap_ms() + + output_df = pd.concat([output_df, _df], ignore_index=True) + # _concat_lap = timer.lap_ms() + + # print( + # f"*********** Real: {_real} ***********\n" + # f"Loc function: {_relative_date_date_lap} ms" + # f"Sub function: {_sub_lap} ms" + # f"Concat function: {_concat_lap} ms" + # ) + + make_date_column_datetime_object(output_df) + + print(f"Calculation Second Groupby REAL took: {timer.elapsed_s():.2f}s") + + return output_df + + +def create_relative_to_date_df_group_by_real_3( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + `Output:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - This function iterates over realization group in input df + - For-loop makes it possible to get realization not present in _relative_date_df + - When realization is not present in _relative_date_df, the realization is excluded + from output df. + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + # Columns of correct dtype + _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} + output_df = pd.DataFrame(_columns) + + _relative_date_df: pd.DataFrame = ( + df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index("REAL") + ) + if _relative_date_df.empty: + # Dataframe with columns, but no rows + return output_df + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + # NOTE: This for-loop will neglect realizations in input df not present in _relative_date_data! + for _realization in _relative_date_df.index: + _df = df.loc[df["REAL"] == _realization].copy() + _relative_date_data = _relative_date_df.loc[_realization] + # _relative_date_date_lap = timer.lap_ms() + + # If realization does not exist in _relative_date_df + if _relative_date_data.empty: + continue + + _df[vectors] = _df[vectors].sub(_relative_date_data, axis=1) + # _sub_lap = timer.lap_ms() + + output_df = pd.concat([output_df, _df], ignore_index=True) + # _concat_lap = timer.lap_ms() + + # print( + # f"*********** Real: {_real} ***********\n" + # f"Loc function: {_relative_date_date_lap} ms" + # f"Sub function: {_sub_lap} ms" + # f"Concat function: {_concat_lap} ms" + # ) + + make_date_column_datetime_object(output_df) + + print(f"Calculation Third Groupby REAL took: {timer.elapsed_s():.2f}s") + + return output_df + + +def create_relative_to_date_df_group_by_real_4( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + `Output:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - This function iterates over realization group in input df + - For-loop makes it possible to get realization not present in _relative_date_df + - When realization is not present in _relative_date_df, the realization is excluded + from output df. + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + # Columns of correct dtype + _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} + output_df = pd.DataFrame(_columns) + + _relative_date_df: pd.DataFrame = ( + df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index("REAL") + ) + if _relative_date_df.empty: + # Dataframe with columns, but no rows + return output_df + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + # NOTE: This for-loop calculates with np.ndarray objects! + output_array = None + for _real in _relative_date_df.index: + _date_reals = df.loc[df["REAL"] == _real][["DATE", "REAL"]].values + _vectors = df.loc[df["REAL"] == _real][vectors].values + _relative_date_data = _relative_date_df.loc[_real].values + + _relative_vectors = _vectors - _relative_date_data + + _relative_matrix = np.column_stack([_date_reals, _relative_vectors]) + + if output_array is None: + output_array = _relative_matrix + else: + output_array = np.append(output_array, _relative_matrix, axis=0) + + output_df = pd.DataFrame(columns=df.columns, data=output_array) + make_date_column_datetime_object(output_df) + + print(f"Calculation Fourth Groupby REAL took: {timer.elapsed_s():.2f}s") + + return output_df + + +def create_relative_to_date_df_group_by_real( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + `Output:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - This function iterates over realization group in _relative_date_df + - For-loop the neglects all realization numbers not present in _relative_date_df + - If a realization in input df is not present in _relative_date_df, the realization + is excluded from output df. + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + # Columns of correct dtype + _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} + output_df = pd.DataFrame(_columns) + + _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( + columns=["DATE"] + ) + if _relative_date_df.empty: + # Dataframe with columns, but no rows + return output_df + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + # NOTE: This for-loop will neglect realizations in input df not present in + # _relative_date_data! + for _real, _real_df in _relative_date_df.groupby("REAL"): + _relative_date_data = _real_df[vectors].iloc[0] + _df = df.loc[df["REAL"] == _real].copy() + _df[vectors] = _df[vectors].sub(_relative_date_data, axis=1) + + output_df = pd.concat([output_df, _df], ignore_index=True) + + make_date_column_datetime_object(output_df) + + print(f"Calculation First Groupby REAL took: {timer.elapsed_s():.2f}s") + + return output_df + + +# pylint: disable=too-many-locals +def create_relative_to_date_df_group_by_date( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE + + TODO: + - OPTIMIZE CODE/ REFACTOR + - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + # Columns of correct dtype + _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} + output_df = pd.DataFrame(_columns) + + _relative_date_df: pd.DataFrame = ( + df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index(["REAL"]) + ) + if _relative_date_df.empty: + # TODO: Return empty dataframe with columns and no rows or input df? + return output_df + + for i, (__, _df) in enumerate(df.groupby("DATE")): + # __ = timer.lap_ms() + # TODO: Simplify code within loop? + _date = _df["DATE"] + _date_index = pd.Index(_date) + + _df.drop(columns=["DATE"], inplace=True) + _df.set_index(["REAL"], inplace=True) + # _relative_date_date_lap = timer.lap_ms() + + # TODO: What if "REAL" is not matching between _relative_date_df and _df + res = _df.sub(_relative_date_df) # .dropna(axis=0, how="any") + # _sub_lap = timer.lap_ms() + res.reset_index(inplace=True) + res.set_index(_date_index, inplace=True) + res.reset_index(inplace=True) + # _reset_index_lap = timer.lap_ms() + + output_df = pd.concat([output_df, res], ignore_index=True) + # _concat_lap = timer.lap_ms() + + # print( + # f"*********** Iteration {i} ***********\n" + # f"Loc function: {_relative_date_date_lap} ms, " + # f"Sub function: {_sub_lap} ms, " + # f"Reset index function: {_reset_index_lap} ms, " + # f"Concat function: {_concat_lap} ms" + # ) + + # TODO: Drop sorting? + output_df.sort_values(["REAL", "DATE"], ignore_index=True, inplace=True) + + make_date_column_datetime_object(output_df) + + print(f"Calculation First Groupby DATE took: {timer.elapsed_s():.2f}s") + + return output_df + + +def create_relative_to_date_df_group_by_date_2( + df: pd.DataFrame, relative_date: datetime.datetime +) -> pd.DataFrame: + """ + Create dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE + + TODO: + - OPTIMIZE CODE/ REFACTOR + - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + # Columns of correct dtype + _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} + output_df = pd.DataFrame(_columns) + + _relative_date_df: pd.DataFrame = ( + df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index(["REAL"]) + ) + if _relative_date_df.empty: + # TODO: Return empty dataframe with columns and no rows or input df? + return output_df + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + for i, (__, _df) in enumerate(df.groupby("DATE")): + # __ = timer.lap_ms() + + # TODO: What if "REAL" is not matching between _relative_date_df and _df + _df[vectors] = _df[vectors].sub(_relative_date_df[vectors]) + # _sub_lap = timer.lap_ms() + + output_df = pd.concat([output_df, _df], ignore_index=True) + # _concat_lap = timer.lap_ms() + + # print( + # f"*********** Iteration {i} ***********\n" + # f"Sub function: {_sub_lap} ms, " + # f"Concat function: {_concat_lap} ms" + # ) + + # TODO: Drop sorting? + output_df.sort_values(["REAL", "DATE"], ignore_index=True, inplace=True) + + make_date_column_datetime_object(output_df) + + print(f"Calculation Second Groupby DATE took: {timer.elapsed_s():.2f}s") + + return output_df + + +def make_relative_to_date_df( + df: pd.DataFrame, relative_date: datetime.datetime +) -> None: + """ + Make dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - This function iterates over realization group in _relative_date_df + - For-loop the neglects all realization numbers not present in _relative_date_df + - If a realization in input df is not present in _relative_date_df, the realization + is excluded from relative to date calculation. I.e. subtraction is not performed + and realization data remain unaffected in df - not calculating relative to date! + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( + columns=["DATE"] + ) + if _relative_date_df.empty: + # Dataframe with columns, but no rows + return + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + # NOTE: This for-loop will not perform subtraction for realizations not present in + # _relative_date_data + for _real, _real_df in _relative_date_df.groupby("REAL"): + _relative_date_data = _real_df[vectors].iloc[0] + df.loc[df["REAL"] == _real, vectors] = df.loc[df["REAL"] == _real, vectors].sub( + _relative_date_data, axis=1 + ) + + make_date_column_datetime_object(df) + + print(f"Calculation with First Make method took {timer.elapsed_s():.2f}s") + + +def make_relative_to_date_df_2( + df: pd.DataFrame, relative_date: datetime.datetime +) -> None: + """ + Make dataframe where data for relative_date is subtracted from respective + vector data. + + I.e. Subtract vector data for set of realizations at give date from vectors + for all dates present in dataframe. + + `Input:` + * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] + + NOTE: + - This function iterates over realization group in _relative_date_df + - For-loop the neglects all realization numbers not present in _relative_date_df + - If a realization in input df is not present in _relative_date_df, the realization + is excluded from relative to date calculation. I.e. subtraction is not performed + and realization data remain unaffected in df - not calculating relative to date! + """ + + assert_date_column_is_datetime_object(df) + + timer = PerfTimer() + + if not set(["DATE", "REAL"]).issubset(set(df.columns)): + raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') + + _relative_date_df: pd.DataFrame = ( + df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index("REAL") + ) + + if _relative_date_df.empty: + # Dataframe with columns, but no rows + return + + vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] + + # NOTE: This for-loop will not perform subtraction for realizations not present in + # _relative_date_data + for _realization in _relative_date_df.index: + _relative_date_data = _relative_date_df.loc[_realization] + df.loc[df["REAL"] == _realization, vectors] = df.loc[ + df["REAL"] == _realization, vectors + ].sub(_relative_date_data, axis=1) + + make_date_column_datetime_object(df) + + print(f"Calculation with Second Make method took {timer.elapsed_s():.2f}s") diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py index b15156a83..7a74ffbfc 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/derived_ensemble_vectors_accessor_utils.py @@ -1,6 +1,5 @@ -from typing import Dict, List, Optional - import datetime +from typing import Dict, List, Optional from webviz_subsurface_components import ExpressionInfo From 5c30c389f4cf9b7ae2e668284c3f5156822bd174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Tue, 8 Feb 2022 14:54:24 +0100 Subject: [PATCH 19/27] Finalize implementation for PR - Set calculation method for data relative to date: Assuming set of realizatios equal on each date for an ensemble - providers are to be updated! - Add unit tests - Clean up code - Add relative date selection to user download data callback --- .../test_utils/test_dataframe_utils.py | 56 +- .../test_utils/test_datetime_utils.py | 50 ++ ...derived_ensemble_vectors_accessor_utils.py | 1 + .../_simulation_time_series/_callbacks.py | 42 +- .../_simulation_time_series/_layout.py | 14 +- ...ed_delta_ensemble_vectors_accessor_impl.py | 17 +- .../derived_ensemble_vectors_accessor_impl.py | 18 +- .../types/provider_set.py | 3 +- .../utils/dataframe_utils.py | 501 +----------------- .../utils/datetime_utils.py | 29 +- 10 files changed, 143 insertions(+), 588 deletions(-) create mode 100644 tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_datetime_utils.py diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py index 1e6125d14..10d54fa94 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_dataframe_utils.py @@ -1,12 +1,10 @@ import datetime import pandas as pd - from pandas._testing import assert_frame_equal from webviz_subsurface.plugins._simulation_time_series.utils.dataframe_utils import ( - create_relative_to_date_df_group_by_real, - create_relative_to_date_df_group_by_real_2, + create_relative_to_date_df, ) @@ -47,19 +45,12 @@ def test_create_relative_to_date_df_consistent_realization_for_all_dates() -> No ], ) - first_method = create_relative_to_date_df_group_by_real( - input_df, datetime.datetime(2263, 1, 1) - ) - second_method = create_relative_to_date_df_group_by_real_2( - input_df, datetime.datetime(2263, 1, 1) + assert_frame_equal( + create_relative_to_date_df(input_df, datetime.datetime(2263, 1, 1)), expected_df ) - assert_frame_equal(first_method, expected_df) - assert_frame_equal(second_method, expected_df) - assert False - -def test_create_relative_to_date_df_relative_date_missing_realization() -> None: +def test_create_relative_to_date_df_relative_date_missing_a_realization() -> None: # Missing datetime.datetime(2263, 1, 1) for real = 3! input_df = pd.DataFrame( columns=["DATE", "REAL", "A", "B"], @@ -91,12 +82,37 @@ def test_create_relative_to_date_df_relative_date_missing_realization() -> None: ], ) - first_method = create_relative_to_date_df_group_by_real( - input_df, datetime.datetime(2263, 1, 1) + assert_frame_equal( + create_relative_to_date_df(input_df, datetime.datetime(2263, 1, 1)), expected_df + ) + + +def test_create_relative_to_date_df_relative_date_not_existing() -> None: + # Missing datetime.datetime(2263, 5, 1) + input_df = pd.DataFrame( + columns=["DATE", "REAL", "A", "B"], + data=[ + [datetime.datetime(2263, 1, 1), 1, 10.0, 130.0], + [datetime.datetime(2263, 2, 1), 1, 45.0, 135.0], + [datetime.datetime(2263, 3, 1), 1, 50.0, 140.0], + [datetime.datetime(2263, 4, 1), 1, 55.0, 145.0], + [datetime.datetime(2263, 1, 1), 2, 11.0, 150.0], + [datetime.datetime(2263, 2, 1), 2, 65.0, 155.0], + [datetime.datetime(2263, 3, 1), 2, 70.0, 160.0], + [datetime.datetime(2263, 4, 1), 2, 75.0, 165.0], + [datetime.datetime(2263, 1, 1), 3, 12.0, 170.0], + [datetime.datetime(2263, 2, 1), 3, 85.0, 175.0], + [datetime.datetime(2263, 3, 1), 3, 90.0, 180.0], + [datetime.datetime(2263, 4, 1), 3, 95.0, 185.0], + ], ) - second_method = create_relative_to_date_df_group_by_real_2( - input_df, datetime.datetime(2263, 1, 1) + + # Ensure dtype for columns for df with no rows + _columns = { + name: pd.Series(dtype=input_df.dtypes[name]) for name in input_df.columns + } + expected_df = pd.DataFrame(_columns) + + assert_frame_equal( + create_relative_to_date_df(input_df, datetime.datetime(2263, 5, 1)), expected_df ) - assert_frame_equal(first_method, expected_df) - assert_frame_equal(second_method, expected_df) - assert False diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_datetime_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_datetime_utils.py new file mode 100644 index 000000000..bc11557f9 --- /dev/null +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_datetime_utils.py @@ -0,0 +1,50 @@ +import datetime + +import pytest + +from webviz_subsurface.plugins._simulation_time_series.utils.datetime_utils import ( + from_str, + to_str, +) + + +def test_from_str_success() -> None: + assert from_str("2021-03-11") == datetime.datetime(2021, 3, 11) + assert from_str("1956-08-26") == datetime.datetime(1956, 8, 26) + + +def test_from_str_assert() -> None: + # Invalid datetime arguments (hour, minute, second, microsecond) + invalid_dates = ["2021-03-11-23-55-11", "1996-05-26-23", "2001-08-11-11-43"] + for _date in invalid_dates: + with pytest.raises(ValueError) as err: + from_str(_date) + assert str(err.value) == f"unconverted data remains: {_date[10:]}" + + +def test_to_str_success() -> None: + assert to_str(datetime.datetime(2021, 6, 13)) == "2021-06-13" + assert to_str(datetime.datetime(2021, 12, 28)) == "2021-12-28" + assert to_str(datetime.datetime(2021, 3, 7, 0)) == "2021-03-07" + assert to_str(datetime.datetime(2021, 10, 22, 0, 0)) == "2021-10-22" + assert to_str(datetime.datetime(2021, 1, 23, 0, 0, 0)) == "2021-01-23" + assert to_str(datetime.datetime(2021, 12, 28, 0, 0, 0, 0)) == "2021-12-28" + + +def test_to_str_assert() -> None: + # Invalid datetime arguments (hour, minute, second, microsecond) + invalid_dates = [ + datetime.datetime(2021, 6, 13, 15, 32, 11, 43), + datetime.datetime(2021, 6, 13, 5, 21, 45), + datetime.datetime(2021, 6, 13, 23, 55), + datetime.datetime(2021, 6, 13, 5), + ] + + for _date in invalid_dates: + with pytest.raises(ValueError) as err: + to_str(_date) + assert ( + str(err.value) + == f"Invalid date resolution, expected no data for hour, minute, second" + f" or microsecond for {str(_date)}" + ) diff --git a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_derived_ensemble_vectors_accessor_utils.py b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_derived_ensemble_vectors_accessor_utils.py index 25d202056..f5ca59caf 100644 --- a/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_derived_ensemble_vectors_accessor_utils.py +++ b/tests/unit_tests/plugin_tests/test_simulation_time_series/test_utils/test_derived_ensemble_vectors_accessor_utils.py @@ -54,6 +54,7 @@ def test_create_derived_vectors_accessor_dict() -> None: expressions=[], delta_ensembles=delta_ensembles, resampling_frequency=None, + relative_date=None, ) assert len(created_result) == len(ensembles) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index d96cfe353..e9da0d69c 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines import copy import datetime from typing import Callable, Dict, List, Optional, Tuple, Union @@ -182,7 +183,7 @@ def _update_graph( vector_calculator_expressions, vectors ) - # Convert from string values to enum types + # Convert from string values to strongly typed visualization = VisualizationOptions(visualization_value) statistics_options = [ StatisticsOptions(elm) for elm in statistics_option_values @@ -200,16 +201,6 @@ def _update_graph( else datetime_utils.from_str(relative_date_value) ) - # ************************** - # - # TODO: REMOVE CODE!!!! - - # print(relative_date_value) - # print(relative_date) - - # - # ************************** - # Prevent update if realization filtering is not affecting pure statistics plot # TODO: Refactor code or create utility for getting trigger ID in a "cleaner" way? ctx = dash.callback_context.triggered @@ -484,6 +475,10 @@ def _update_graph( get_uuid(LayoutElements.VECTOR_CALCULATOR_EXPRESSIONS), "data", ), + State( + get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), + "value", + ), ], ) def _user_download_data( @@ -496,6 +491,7 @@ def _user_download_data( statistics_calculated_from_value: str, delta_ensembles: List[DeltaEnsemble], vector_calculator_expressions: List[ExpressionInfo], + relative_date_value: str, ) -> Union[EncodedFile, str]: """Callback to download data based on selections @@ -521,11 +517,17 @@ def _user_download_data( vector_calculator_expressions, vectors ) - # Convert from string values to enum types + # Convert from string values to strongly typed visualization = VisualizationOptions(visualization_value) resampling_frequency = Frequency.from_string_value(resampling_frequency_value) statistics_from_option = StatisticsFromOptions(statistics_calculated_from_value) + relative_date: Optional[datetime.datetime] = ( + None + if relative_date_value is None + else datetime_utils.from_str(relative_date_value) + ) + # Create dict of derived vectors accessors for selected ensembles derived_vectors_accessors: Dict[ str, DerivedVectorsAccessor @@ -536,7 +538,7 @@ def _user_download_data( expressions=selected_expressions, delta_ensembles=delta_ensembles, resampling_frequency=resampling_frequency, - relative_date=None, + relative_date=relative_date, ) # Dict with vector name as key and dataframe data as value @@ -591,6 +593,10 @@ def _user_download_data( # Append data for each vector for vectors_df in vectors_df_list: + # Ensure rows of data + if vectors_df.shape[0] <= 0: + continue + vector_names = [ elm for elm in vectors_df.columns if elm not in ["DATE", "REAL"] ] @@ -976,16 +982,6 @@ def _update_relative_date_dropdown( for _date in dates_union ] - # ******************************************************************** - # TODO: REMOVE!! Only added to obtain invalid date for testing! - new_relative_date_options.append( - { - "label": datetime_utils.to_str(datetime.datetime(2264, 1, 1)), - "value": datetime_utils.to_str(datetime.datetime(2264, 1, 1)), - } - ) - # ******************************************************************** - # Create valid dropdown value: new_relative_date_value = next( ( diff --git a/webviz_subsurface/plugins/_simulation_time_series/_layout.py b/webviz_subsurface/plugins/_simulation_time_series/_layout.py index 36b009495..6bbdf870e 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_layout.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_layout.py @@ -198,10 +198,19 @@ def __settings_layout( for frequency in Frequency ], value=selected_resampling_frequency, + style={ + "margin-bottom": "10px", + }, + ), + wcc.Label( + "Data relative to date:", + style={ + "font-style": "italic", + }, ), wcc.Dropdown( - label="Data relative to date", clearable=True, + disabled=disable_resampling_dropdown, id=get_uuid(LayoutElements.RELATIVE_DATE_DROPDOWN), options=[ { @@ -210,9 +219,6 @@ def __settings_layout( } for _date in sorted(ensembles_dates) ], - style={ - "margin-top": "5px", - }, ), wcc.Label( "NB: Disabled for presampled data", diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py index f097cb534..a9572ac87 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_delta_ensemble_vectors_accessor_impl.py @@ -184,16 +184,15 @@ def get_provider_vectors_df( f'Vector data handler for provider "{self._name}" has no provider vectors' ) - if not self._relative_date: - return self.__create_delta_ensemble_vectors_df( - self._provider_vectors, self._resampling_frequency, realizations + if self._relative_date: + return dataframe_utils.create_relative_to_date_df( + self.__create_delta_ensemble_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations + ), + self._relative_date, ) - - return dataframe_utils.create_relative_to_date_df( - self.__create_delta_ensemble_vectors_df( - self._provider_vectors, self._resampling_frequency, realizations - ), - self._relative_date, + return self.__create_delta_ensemble_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations ) def create_per_interval_and_per_day_vectors_df( diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py index 9e22385eb..67e9c6a1a 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/derived_ensemble_vectors_accessor_impl.py @@ -85,16 +85,16 @@ def get_provider_vectors_df( raise ValueError( f'Vector data handler for provider "{self._name}" has no provider vectors' ) - if not self._relative_date: - return self._provider.get_vectors_df( - self._provider_vectors, self._resampling_frequency, realizations - ) - return dataframe_utils.create_relative_to_date_df( - self._provider.get_vectors_df( - self._provider_vectors, self._resampling_frequency, realizations - ), - self._relative_date, + if self._relative_date: + return dataframe_utils.create_relative_to_date_df( + self._provider.get_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations + ), + self._relative_date, + ) + return self._provider.get_vectors_df( + self._provider_vectors, self._resampling_frequency, realizations ) def create_per_interval_and_per_day_vectors_df( diff --git a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py index 01c1a74a2..272e76d0e 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py +++ b/webviz_subsurface/plugins/_simulation_time_series/types/provider_set.py @@ -107,8 +107,7 @@ def all_dates( resampling_frequency: Optional[Frequency], ) -> List[datetime.datetime]: """List with the union of dates among providers""" - # TODO: Verify method - delete or modify? - # Consider adding argument: realizations: Optional[Sequence[int]] = None + # TODO: Adjust when providers are updated! dates_union: Set[datetime.datetime] = set() for provider in self.all_providers(): _dates = set(provider.dates(resampling_frequency, None)) diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py index cbeaf1309..74f9c38c3 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py @@ -1,48 +1,25 @@ import datetime -import numpy as np import pandas as pd from webviz_subsurface._utils.dataframe_utils import ( assert_date_column_is_datetime_object, make_date_column_datetime_object, ) -from webviz_subsurface._utils.perf_timer import PerfTimer - -# ***************************************** -# -# TODO: Verify which function is best! -# -# NOTE: Testing with timer scores create_relative_to_date_df_2 as winner! -# -# ***************************************** def create_relative_to_date_df( df: pd.DataFrame, relative_date: datetime.datetime -) -> pd.DataFrame: - df_copy = df.copy() - df_copy_2 = df.copy() - print("****************************************") - make_relative_to_date_df(df_copy, relative_date) - make_relative_to_date_df_2(df_copy_2, relative_date) - create_relative_to_date_df_group_by_real(df, relative_date) - create_relative_to_date_df_group_by_real_2(df, relative_date) - create_relative_to_date_df_group_by_real_4(df, relative_date) - # create_relative_to_date_df_group_by_date(df, relative_date) - # create_relative_to_date_df_group_by_date_2(df, relative_date) - return create_relative_to_date_df_group_by_real_3(df, relative_date) - - -def create_relative_to_date_df_group_by_real_2( - df: pd.DataFrame, relative_date: datetime.datetime ) -> pd.DataFrame: """ Create dataframe where data for relative_date is subtracted from respective vector data. - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. + I.e. Subtract realization data at given relative date from corresponding + realizations at each individual date for each vector column in dataframe. + + `Assume:` + Set of realizations are equal for each date in "DATE" column of input dataframe. `Input:` * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] @@ -51,16 +28,13 @@ def create_relative_to_date_df_group_by_real_2( * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] NOTE: - - This function iterates over realization group in input df - - For-loop makes it possible to get realization not present in _relative_date_df - - When realization is not present in _relative_date_df, the realization is excluded - from output df. + - This function iterates over realization group in input dataframe + - For-loop makes it possible to get realization not present in _relative_date_df, if + realization is not present in _relative_date_df the realization is excluded output. """ assert_date_column_is_datetime_object(df) - timer = PerfTimer() - if not set(["DATE", "REAL"]).issubset(set(df.columns)): raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') @@ -79,475 +53,16 @@ def create_relative_to_date_df_group_by_real_2( # NOTE: This for-loop makes it possible to get real not represented in _relative_date_df! for _real, _df in df.groupby("REAL"): - # __ = timer.lap_ms() _relative_date_data = _relative_date_df.loc[ _relative_date_df["REAL"] == _real ].drop(columns=["REAL"]) - # _relative_date_date_lap = timer.lap_ms() # If realization does not exist in _relative_date_df if _relative_date_data.empty: continue _df[vectors] = _df[vectors].sub(_relative_date_data.iloc[0], axis=1) - # _sub_lap = timer.lap_ms() - output_df = pd.concat([output_df, _df], ignore_index=True) - # _concat_lap = timer.lap_ms() - - # print( - # f"*********** Real: {_real} ***********\n" - # f"Loc function: {_relative_date_date_lap} ms" - # f"Sub function: {_sub_lap} ms" - # f"Concat function: {_concat_lap} ms" - # ) - - make_date_column_datetime_object(output_df) - - print(f"Calculation Second Groupby REAL took: {timer.elapsed_s():.2f}s") - - return output_df - - -def create_relative_to_date_df_group_by_real_3( - df: pd.DataFrame, relative_date: datetime.datetime -) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - `Output:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: - - This function iterates over realization group in input df - - For-loop makes it possible to get realization not present in _relative_date_df - - When realization is not present in _relative_date_df, the realization is excluded - from output df. - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - # Columns of correct dtype - _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} - output_df = pd.DataFrame(_columns) - - _relative_date_df: pd.DataFrame = ( - df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index("REAL") - ) - if _relative_date_df.empty: - # Dataframe with columns, but no rows - return output_df - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - - # NOTE: This for-loop will neglect realizations in input df not present in _relative_date_data! - for _realization in _relative_date_df.index: - _df = df.loc[df["REAL"] == _realization].copy() - _relative_date_data = _relative_date_df.loc[_realization] - # _relative_date_date_lap = timer.lap_ms() - - # If realization does not exist in _relative_date_df - if _relative_date_data.empty: - continue - - _df[vectors] = _df[vectors].sub(_relative_date_data, axis=1) - # _sub_lap = timer.lap_ms() - - output_df = pd.concat([output_df, _df], ignore_index=True) - # _concat_lap = timer.lap_ms() - - # print( - # f"*********** Real: {_real} ***********\n" - # f"Loc function: {_relative_date_date_lap} ms" - # f"Sub function: {_sub_lap} ms" - # f"Concat function: {_concat_lap} ms" - # ) - - make_date_column_datetime_object(output_df) - - print(f"Calculation Third Groupby REAL took: {timer.elapsed_s():.2f}s") - - return output_df - - -def create_relative_to_date_df_group_by_real_4( - df: pd.DataFrame, relative_date: datetime.datetime -) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - `Output:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: - - This function iterates over realization group in input df - - For-loop makes it possible to get realization not present in _relative_date_df - - When realization is not present in _relative_date_df, the realization is excluded - from output df. - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - # Columns of correct dtype - _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} - output_df = pd.DataFrame(_columns) - _relative_date_df: pd.DataFrame = ( - df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index("REAL") - ) - if _relative_date_df.empty: - # Dataframe with columns, but no rows - return output_df - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - - # NOTE: This for-loop calculates with np.ndarray objects! - output_array = None - for _real in _relative_date_df.index: - _date_reals = df.loc[df["REAL"] == _real][["DATE", "REAL"]].values - _vectors = df.loc[df["REAL"] == _real][vectors].values - _relative_date_data = _relative_date_df.loc[_real].values - - _relative_vectors = _vectors - _relative_date_data - - _relative_matrix = np.column_stack([_date_reals, _relative_vectors]) - - if output_array is None: - output_array = _relative_matrix - else: - output_array = np.append(output_array, _relative_matrix, axis=0) - - output_df = pd.DataFrame(columns=df.columns, data=output_array) make_date_column_datetime_object(output_df) - - print(f"Calculation Fourth Groupby REAL took: {timer.elapsed_s():.2f}s") - return output_df - - -def create_relative_to_date_df_group_by_real( - df: pd.DataFrame, relative_date: datetime.datetime -) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - `Output:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: - - This function iterates over realization group in _relative_date_df - - For-loop the neglects all realization numbers not present in _relative_date_df - - If a realization in input df is not present in _relative_date_df, the realization - is excluded from output df. - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - # Columns of correct dtype - _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} - output_df = pd.DataFrame(_columns) - - _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( - columns=["DATE"] - ) - if _relative_date_df.empty: - # Dataframe with columns, but no rows - return output_df - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - - # NOTE: This for-loop will neglect realizations in input df not present in - # _relative_date_data! - for _real, _real_df in _relative_date_df.groupby("REAL"): - _relative_date_data = _real_df[vectors].iloc[0] - _df = df.loc[df["REAL"] == _real].copy() - _df[vectors] = _df[vectors].sub(_relative_date_data, axis=1) - - output_df = pd.concat([output_df, _df], ignore_index=True) - - make_date_column_datetime_object(output_df) - - print(f"Calculation First Groupby REAL took: {timer.elapsed_s():.2f}s") - - return output_df - - -# pylint: disable=too-many-locals -def create_relative_to_date_df_group_by_date( - df: pd.DataFrame, relative_date: datetime.datetime -) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE - - TODO: - - OPTIMIZE CODE/ REFACTOR - - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - # Columns of correct dtype - _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} - output_df = pd.DataFrame(_columns) - - _relative_date_df: pd.DataFrame = ( - df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index(["REAL"]) - ) - if _relative_date_df.empty: - # TODO: Return empty dataframe with columns and no rows or input df? - return output_df - - for i, (__, _df) in enumerate(df.groupby("DATE")): - # __ = timer.lap_ms() - # TODO: Simplify code within loop? - _date = _df["DATE"] - _date_index = pd.Index(_date) - - _df.drop(columns=["DATE"], inplace=True) - _df.set_index(["REAL"], inplace=True) - # _relative_date_date_lap = timer.lap_ms() - - # TODO: What if "REAL" is not matching between _relative_date_df and _df - res = _df.sub(_relative_date_df) # .dropna(axis=0, how="any") - # _sub_lap = timer.lap_ms() - res.reset_index(inplace=True) - res.set_index(_date_index, inplace=True) - res.reset_index(inplace=True) - # _reset_index_lap = timer.lap_ms() - - output_df = pd.concat([output_df, res], ignore_index=True) - # _concat_lap = timer.lap_ms() - - # print( - # f"*********** Iteration {i} ***********\n" - # f"Loc function: {_relative_date_date_lap} ms, " - # f"Sub function: {_sub_lap} ms, " - # f"Reset index function: {_reset_index_lap} ms, " - # f"Concat function: {_concat_lap} ms" - # ) - - # TODO: Drop sorting? - output_df.sort_values(["REAL", "DATE"], ignore_index=True, inplace=True) - - make_date_column_datetime_object(output_df) - - print(f"Calculation First Groupby DATE took: {timer.elapsed_s():.2f}s") - - return output_df - - -def create_relative_to_date_df_group_by_date_2( - df: pd.DataFrame, relative_date: datetime.datetime -) -> pd.DataFrame: - """ - Create dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: THIS IS A PROTOTYPE, WHICH IS NOT OPTIMAL FOR PERFORMANCE - - TODO: - - OPTIMIZE CODE/ REFACTOR - - HOW TO HANDLE IF relative_date does not exist in one REAL? .dropna()? - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - # Columns of correct dtype - _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} - output_df = pd.DataFrame(_columns) - - _relative_date_df: pd.DataFrame = ( - df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index(["REAL"]) - ) - if _relative_date_df.empty: - # TODO: Return empty dataframe with columns and no rows or input df? - return output_df - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - - for i, (__, _df) in enumerate(df.groupby("DATE")): - # __ = timer.lap_ms() - - # TODO: What if "REAL" is not matching between _relative_date_df and _df - _df[vectors] = _df[vectors].sub(_relative_date_df[vectors]) - # _sub_lap = timer.lap_ms() - - output_df = pd.concat([output_df, _df], ignore_index=True) - # _concat_lap = timer.lap_ms() - - # print( - # f"*********** Iteration {i} ***********\n" - # f"Sub function: {_sub_lap} ms, " - # f"Concat function: {_concat_lap} ms" - # ) - - # TODO: Drop sorting? - output_df.sort_values(["REAL", "DATE"], ignore_index=True, inplace=True) - - make_date_column_datetime_object(output_df) - - print(f"Calculation Second Groupby DATE took: {timer.elapsed_s():.2f}s") - - return output_df - - -def make_relative_to_date_df( - df: pd.DataFrame, relative_date: datetime.datetime -) -> None: - """ - Make dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: - - This function iterates over realization group in _relative_date_df - - For-loop the neglects all realization numbers not present in _relative_date_df - - If a realization in input df is not present in _relative_date_df, the realization - is excluded from relative to date calculation. I.e. subtraction is not performed - and realization data remain unaffected in df - not calculating relative to date! - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( - columns=["DATE"] - ) - if _relative_date_df.empty: - # Dataframe with columns, but no rows - return - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - - # NOTE: This for-loop will not perform subtraction for realizations not present in - # _relative_date_data - for _real, _real_df in _relative_date_df.groupby("REAL"): - _relative_date_data = _real_df[vectors].iloc[0] - df.loc[df["REAL"] == _real, vectors] = df.loc[df["REAL"] == _real, vectors].sub( - _relative_date_data, axis=1 - ) - - make_date_column_datetime_object(df) - - print(f"Calculation with First Make method took {timer.elapsed_s():.2f}s") - - -def make_relative_to_date_df_2( - df: pd.DataFrame, relative_date: datetime.datetime -) -> None: - """ - Make dataframe where data for relative_date is subtracted from respective - vector data. - - I.e. Subtract vector data for set of realizations at give date from vectors - for all dates present in dataframe. - - `Input:` - * df - `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] - - NOTE: - - This function iterates over realization group in _relative_date_df - - For-loop the neglects all realization numbers not present in _relative_date_df - - If a realization in input df is not present in _relative_date_df, the realization - is excluded from relative to date calculation. I.e. subtraction is not performed - and realization data remain unaffected in df - not calculating relative to date! - """ - - assert_date_column_is_datetime_object(df) - - timer = PerfTimer() - - if not set(["DATE", "REAL"]).issubset(set(df.columns)): - raise ValueError('Expect column "DATE" and "REAL" in input dataframe!') - - _relative_date_df: pd.DataFrame = ( - df.loc[df["DATE"] == relative_date].drop(columns=["DATE"]).set_index("REAL") - ) - - if _relative_date_df.empty: - # Dataframe with columns, but no rows - return - - vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - - # NOTE: This for-loop will not perform subtraction for realizations not present in - # _relative_date_data - for _realization in _relative_date_df.index: - _relative_date_data = _relative_date_df.loc[_realization] - df.loc[df["REAL"] == _realization, vectors] = df.loc[ - df["REAL"] == _realization, vectors - ].sub(_relative_date_data, axis=1) - - make_date_column_datetime_object(df) - - print(f"Calculation with Second Make method took {timer.elapsed_s():.2f}s") diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py index b72ac31b6..3c5dd187d 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/datetime_utils.py @@ -8,36 +8,9 @@ def from_str(date_str: str) -> datetime.datetime: def to_str(date: datetime.datetime) -> str: - if ( - date.hour != 0 - and date.minute != 0 - and date.second != 0 - and date.microsecond != 0 - ): + if date.hour != 0 or date.minute != 0 or date.second != 0 or date.microsecond != 0: raise ValueError( f"Invalid date resolution, expected no data for hour, minute, second" f" or microsecond for {str(date)}" ) return date.strftime("%Y-%m-%d") - - -# from dateutil import parser - -# NOTE: ADD "python-dateutil>=2.8.2", to setup.py if so! - -# def from_str(date_str: str) -> datetime.datetime: -# return parser.parse(date_str) - - -# def to_str(date: datetime.datetime) -> str: -# str() -# if ( -# date.hour != 0 -# and date.minute != 0 -# and date.second != 0 -# and date.microsecond != 0 -# ): -# raise ValueError( -# f"Invalid date resolution, expected no data finer than day for {str(date)}" -# ) -# return date.strftime("%m-%d-%y") From 5f3dc1b3b5caee26c25dbe0a1db52b2cd1d6422a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Tue, 8 Feb 2022 15:14:09 +0100 Subject: [PATCH 20/27] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c59dc4f2f..57ae2823a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Added configurable user defined vector definitions. +- [#951](https://github.com/equinor/webviz-subsurface/pull/951) - `SimulationTimeSeries` - Added calculating delta relative to date within ensemble - i.e. subtract realization values on selected date from corresponding realization values for each date in the selected vectors. ### Changed From cf07c6a5f42f60f803c0a4fa54ea9cea33c7e1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Wed, 9 Feb 2022 12:50:20 +0100 Subject: [PATCH 21/27] Update webviz_subsurface/_utils/vector_selector.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Fredrik Kiær <31612826+anders-kiaer@users.noreply.github.com> --- webviz_subsurface/_utils/vector_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/_utils/vector_selector.py b/webviz_subsurface/_utils/vector_selector.py index 05ae8681e..d028c3497 100644 --- a/webviz_subsurface/_utils/vector_selector.py +++ b/webviz_subsurface/_utils/vector_selector.py @@ -24,7 +24,7 @@ def add_vector_to_vector_selector_data( } if not description_at_last_node and description and index == 0: node_data["description"] = description - if description_at_last_node and description and (index == len(nodes) - 1): + elif description_at_last_node and description and (index == len(nodes) - 1): node_data["description"] = description current_child_list.append(node_data) From 027ba1cd2e32f1652f57e19d9b75ed4f653489f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Wed, 9 Feb 2022 12:50:28 +0100 Subject: [PATCH 22/27] Update webviz_subsurface/plugins/_simulation_time_series/_plugin.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Fredrik Kiær <31612826+anders-kiaer@users.noreply.github.com> --- webviz_subsurface/plugins/_simulation_time_series/_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py index 60fe50990..2bccd286c 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_plugin.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_plugin.py @@ -72,7 +72,7 @@ def __init__( self._webviz_settings = webviz_settings self._obsfile = obsfile - # Retreive user defined vector descriptions from configuration and validate + # Retrieve user defined vector descriptions from configuration and validate self._user_defined_vector_descriptions_path = ( None if user_defined_vector_definitions is None From c45c1d26ffcb6d1f500bbd470180e0e7f97e9253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Wed, 9 Feb 2022 12:55:01 +0100 Subject: [PATCH 23/27] Correct changelog --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9358d902..e18ba0946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [UNRELEASED] - YYYY-MM-DD +### Added + +- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Added configurable user defined vector definitions. + +### Changed + +- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Changed vector annotation from "AVG_" with suffix "R" and "INTVL_" to "PER_DAY_" and "PER_INTVL_". Retrieve `VectorDefinitions` via Python-API for `webviz-subsurface-components`. + ## [0.2.10] - 2022-02-09 ### Fixed @@ -17,13 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#932](https://github.com/equinor/webviz-subsurface/pull/932) - `RftPlotter` - Fixed bug related to calculated correlations returning `NaN` if the response variable has constant values. - [#937](https://github.com/equinor/webviz-subsurface/pull/937) - `SimulationTimeSeries` - Fixed hover info for observation trace -### Added -- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Added configurable user defined vector definitions. - - ### Changed -- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Changed vector annotation from "AVG_" with suffix "R" and "INTVL_" to "PER_DAY_" and "PER_INTVL_". Retrieve `VectorDefinitions` via Python-API for `webviz-subsurface-components`. - [#924](https://github.com/equinor/webviz-subsurface/pull/924) - Improvements to the `ParameterFilter` functionality, e.g information regarding active filters and which relizatons are filtered out, and better handling of multiple ensembles. Improvements to the `ParameterAnalysis` plugin, e.g. added boxplot, fixed table formatting and update of parameters based on selected ensemble. - [#935](https://github.com/equinor/webviz-subsurface/pull/935) - Deprecated plugin `ReservoirSimulationTimeSeries`. This has been replaced by the faster, more flexible and less memory hungry plugin `SimulationTimeSeries`. From 9d424ee00a92c3a9c00df4bf827663c2069f8aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:47:23 +0100 Subject: [PATCH 24/27] Update webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Fredrik Kiær <31612826+anders-kiaer@users.noreply.github.com> --- .../plugins/_simulation_time_series/utils/vector_statistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py b/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py index 15d29a3b2..164f08738 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/vector_statistics.py @@ -35,7 +35,7 @@ def create_vectors_statistics_df(vectors_df: pd.DataFrame) -> pd.DataFrame: ) # If no rows of data: - if vectors_df.shape[0] <= 0: + if not vectors_df.shape[0]: columns_tuples = [("DATE", "")] for vector in vector_names: columns_tuples.extend( From 52c930542b61528f9a4c68bd6f29384137d099f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:47:29 +0100 Subject: [PATCH 25/27] Update webviz_subsurface/plugins/_simulation_time_series/_callbacks.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Fredrik Kiær <31612826+anders-kiaer@users.noreply.github.com> --- webviz_subsurface/plugins/_simulation_time_series/_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index e9da0d69c..c106144c4 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -594,7 +594,7 @@ def _user_download_data( # Append data for each vector for vectors_df in vectors_df_list: # Ensure rows of data - if vectors_df.shape[0] <= 0: + if not vectors_df.shape[0]: continue vector_names = [ From 6c865a3a25851544bae7258047253e9372cc6dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:49:04 +0100 Subject: [PATCH 26/27] Update webviz_subsurface/plugins/_simulation_time_series/_callbacks.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Fredrik Kiær <31612826+anders-kiaer@users.noreply.github.com> --- webviz_subsurface/plugins/_simulation_time_series/_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py index c106144c4..fd8cf9e0e 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py +++ b/webviz_subsurface/plugins/_simulation_time_series/_callbacks.py @@ -324,7 +324,7 @@ def _update_graph( for vectors_df in vectors_df_list: # Ensure rows of data - if vectors_df.shape[0] <= 0: + if not vectors_df.shape[0]: continue if visualization == VisualizationOptions.REALIZATIONS: From 88fd605d383357600b56c5f21c37861a6c27445d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= Date: Thu, 10 Feb 2022 15:58:58 +0100 Subject: [PATCH 27/27] Fix based on review --- .../utils/dataframe_utils.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py b/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py index 74f9c38c3..3987ef9e7 100644 --- a/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py +++ b/webviz_subsurface/plugins/_simulation_time_series/utils/dataframe_utils.py @@ -29,8 +29,8 @@ def create_relative_to_date_df( NOTE: - This function iterates over realization group in input dataframe - - For-loop makes it possible to get realization not present in _relative_date_df, if - realization is not present in _relative_date_df the realization is excluded output. + - For-loop makes it possible to get realization not present in relative_date_df, if + realization is not present in relative_date_df the realization is excluded output. """ assert_date_column_is_datetime_object(df) @@ -42,27 +42,27 @@ def create_relative_to_date_df( _columns = {name: pd.Series(dtype=df.dtypes[name]) for name in df.columns} output_df = pd.DataFrame(_columns) - _relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( + relative_date_df: pd.DataFrame = df.loc[df["DATE"] == relative_date].drop( columns=["DATE"] ) - if _relative_date_df.empty: + if relative_date_df.empty: # Dataframe with columns, but no rows return output_df vectors = [elm for elm in df.columns if elm not in ("DATE", "REAL")] - # NOTE: This for-loop makes it possible to get real not represented in _relative_date_df! - for _real, _df in df.groupby("REAL"): - _relative_date_data = _relative_date_df.loc[ - _relative_date_df["REAL"] == _real + # NOTE: This for-loop makes it possible to get real not represented in relative_date_df! + for real, real_df in df.groupby("REAL"): + relative_date_data = relative_date_df.loc[ + relative_date_df["REAL"] == real ].drop(columns=["REAL"]) - # If realization does not exist in _relative_date_df - if _relative_date_data.empty: + # If realization does not exist in relative_date_df + if relative_date_data.empty: continue - _df[vectors] = _df[vectors].sub(_relative_date_data.iloc[0], axis=1) - output_df = pd.concat([output_df, _df], ignore_index=True) + real_df[vectors] = real_df[vectors].sub(relative_date_data.iloc[0], axis=1) + output_df = pd.concat([output_df, real_df], ignore_index=True) make_date_column_datetime_object(output_df) return output_df