def add_realization_traces(self, ensemble: str, vector: str, real_filter: pd.Series = None) -> list: """Renders line trace for each realization, includes history line if present""" dataframe = self.dataframe[self.dataframe["ENSEMBLE"] == ensemble] dataframe = (dataframe[dataframe["REAL"].isin(real_filter)] if real_filter is not None else dataframe) traces = [{ "line": { "shape": self.get_line_shape(vector) }, "x": list(real_df["DATE"]), "y": list(real_df[vector]), "name": ensemble, "customdata": real, "legendgroup": ensemble, "marker": { "color": "red" }, "showlegend": real_idx == 0, } for real_idx, (real, real_df) in enumerate(dataframe.groupby("REAL")) ] if (historical_vector(vector=vector, smry_meta=self.metadata) in dataframe.columns): traces.append( self.add_history_trace( dataframe, historical_vector(vector=vector, smry_meta=self.metadata))) return traces
def add_statistic_traces(self, ensembles: list, vector: str) -> list: """Calculate statistics for a given vector for relevant ensembles""" quantiles = [10, 90] traces = [] ensembles = ensembles if isinstance(ensembles, list) else [ensembles] dataframe = self.dataframe[self.dataframe["ENSEMBLE"].isin(ensembles)] for ensemble, ens_df in dataframe.groupby("ENSEMBLE"): dframe = ens_df.drop(columns=["ENSEMBLE", "REAL"]).groupby("DATE") # Build a dictionary of dataframes to be concatenated dframes = {} dframes["mean"] = dframe.mean() for quantile in quantiles: quantile_str = "p" + str(quantile) dframes[quantile_str] = dframe.quantile(q=quantile / 100.0) dframes["maximum"] = dframe.max() dframes["minimum"] = dframe.min() traces.extend( add_fanchart_traces( pd.concat(dframes, names=["STATISTIC"], sort=False)[vector], self.ens_colors.get( ensemble, self.ens_colors[list(self.ens_colors.keys())[0]]), ensemble, self.get_line_shape(vector), )) if (historical_vector(vector=vector, smry_meta=self.metadata) in dataframe.columns): traces.append( self.add_history_trace( dataframe, historical_vector(vector=vector, smry_meta=self.metadata))) return traces
def add_ensset_realization_traces(self, ensembles: Union[str, list], vector: str) -> list: """Renders line trace for each realization grouped by ensemble, includes history line if present""" ensembles = ensembles if isinstance(ensembles, list) else [ensembles] dataframe = self.dataframe[self.dataframe["ENSEMBLE"].isin(ensembles)] traces = [{ "line": { "shape": self.get_line_shape(vector) }, "x": list(real_df["DATE"]), "y": list(real_df[vector]), "hovertext": f"Realization: {real}, Ensemble: {ensemble}", "name": ensemble, "legendgroup": ensemble, "marker": { "color": self.ens_colors.get( ensemble, self.ens_colors[list(self.ens_colors.keys())[0]]) }, "showlegend": real_idx == 0, } for ens_no, (ensemble, ens_df) in enumerate(dataframe.groupby("ENSEMBLE")) for real_idx, (real, real_df) in enumerate(ens_df.groupby("REAL"))] if (historical_vector(vector=vector, smry_meta=self.metadata) in dataframe.columns): traces.append( self.add_history_trace( dataframe, historical_vector(vector=vector, smry_meta=self.metadata))) return traces
def vectors(self) -> list: return [ c for c in self.dataframe.columns if c not in ["REAL", "ENSEMBLE", "DATE"] and not historical_vector(c, self.metadata, False) in self.dataframe.columns ]
def _determine_vector_names(dataframe: pd.DataFrame, metadata: Optional[pd.DataFrame]) -> List[str]: """Determine which vectors we should make available""" vecnames = [ c for c in dataframe.columns if c not in ["REAL", "ENSEMBLE", "DATE"] and not historical_vector(c, metadata, False) in dataframe.columns ] return vecnames
def get_historical_vector_df(self, vector: str, ensemble: str) -> Optional[pd.DataFrame]: df = self._dataframe hist_vecname = historical_vector(vector, None) if hist_vecname and hist_vecname in df.columns: return (df[[hist_vecname, "DATE"]].loc[(df["REAL"] == df["REAL"].unique()[0]) & (df["ENSEMBLE"] == ensemble)].rename( columns={hist_vecname: vector})) return None
def get_historical_vector_df( self, vector: str, ensemble: str ) -> Optional[pd.DataFrame]: hist_vecname = historical_vector(vector, smry_meta=None) if hist_vecname and hist_vecname in self.vectors: provider = self._provider_set[ensemble] return provider.get_vectors_df( [hist_vecname], None, realizations=provider.realizations()[:1] ).rename(columns={hist_vecname: vector}) return None
def __init__( self, dataframe: pd.DataFrame, theme: dict = None, metadata: Optional[pd.DataFrame] = None, line_shape_fallback: str = "linear", ) -> None: self._dataframe = dataframe self._prepare_and_validate_data() self.theme = theme self._metadata = metadata self.line_shape_fallback = set_simulation_line_shape_fallback( line_shape_fallback) self._vectors = [ c for c in self.dataframe.columns if c not in ["REAL", "ENSEMBLE", "DATE"] and not historical_vector( c, self.metadata, False) in self.dataframe.columns ] self._vector_groups = self._split_vectors_by_type() self._dates = sorted(self._dataframe["DATE"].unique())
def __init__( self, app: dash.Dash, webviz_settings: WebvizSettings, ensembles: Optional[list] = None, rel_file_pattern: str = "share/results/unsmry/*.arrow", perform_presampling: bool = False, obsfile: Path = None, options: dict = None, sampling: str = Frequency.MONTHLY.value, predefined_expressions: str = None, user_defined_vector_definitions: str = None, line_shape_fallback: str = "linear", ) -> None: super().__init__() # NOTE: Temporary css, pending on new wcc modal component. # See: https://github.com/equinor/webviz-core-components/issues/163 WEBVIZ_ASSETS.add( Path(webviz_subsurface.__file__).parent / "_assets" / "css" / "modal.css") self._webviz_settings = webviz_settings self._obsfile = obsfile # Retrieve user defined vector descriptions from configuration and validate self._user_defined_vector_descriptions_path = ( None if user_defined_vector_definitions is None else webviz_settings.shared_settings["user_defined_vector_definitions"] [user_defined_vector_definitions]) 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._custom_vector_definitions = copy.deepcopy( self._user_defined_vector_definitions) self._line_shape_fallback = set_simulation_line_shape_fallback( line_shape_fallback) # Must define valid freqency! if Frequency.from_string_value(sampling) is None: raise ValueError( 'Sampling frequency conversion is "None", i.e. Raw sampling, and ' "is not supported by plugin yet!") self._sampling = Frequency(sampling) self._presampled_frequency = None # TODO: Update functionality when allowing raw data and csv file input # 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] = { ensemble_name: webviz_settings.shared_settings["scratch_ensembles"] [ensemble_name] for ensemble_name in ensembles } if perform_presampling: self._presampled_frequency = self._sampling self._input_provider_set = create_presampled_provider_set_from_paths( ensemble_paths, rel_file_pattern, self._presampled_frequency) else: self._input_provider_set = create_lazy_provider_set_from_paths( ensemble_paths, rel_file_pattern) else: raise ValueError('Incorrect argument, must provide "ensembles"') if not self._input_provider_set: raise ValueError( "Initial provider set is undefined, and ensemble summary providers" " are not instanciated for plugin") self._theme = webviz_settings.theme self._observations = {} if self._obsfile: self._observations = check_and_format_observations( get_path(self._obsfile)) # NOTE: Initially keep set of all vector names - can make dynamic if wanted? vector_names = self._input_provider_set.all_vector_names() non_historical_vector_names = [ vector for vector in vector_names if historical_vector(vector, None, False) not in vector_names ] # NOTE: Initially: With set of vector names, the vector selector data is static # Can be made dynamic based on selected ensembles - i.e. vectors present among # selected providers? self._vector_selector_base_data: list = [] self._vector_calculator_data: list = [] for vector in non_historical_vector_names: add_vector_to_vector_selector_data( self._vector_selector_base_data, vector, ) # Only vectors from providers are provided to vector calculator add_vector_to_vector_selector_data( self._vector_calculator_data, vector, ) metadata = (self._input_provider_set.vector_metadata(vector) if self._input_provider_set else None) 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. 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, per_day_vec, ) add_vector_to_vector_selector_data( self._vector_selector_base_data, per_intvl_vec, ) # Add vector base to custom vector definition if not existing 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] = wsc.VectorDefinition( type=_type, description=simulation_vector_description( per_day_vec_base, self._user_defined_vector_definitions), ) if per_intvl_vec_base not in self._custom_vector_definitions: self._custom_vector_definitions[ per_intvl_vec_base] = wsc.VectorDefinition( type=_type, description=simulation_vector_description( per_intvl_vec_base, self._user_defined_vector_definitions), ) # Retreive predefined expressions from configuration and validate self._predefined_expressions_path = ( None if predefined_expressions is None else webviz_settings. shared_settings["predefined_expressions"][predefined_expressions]) self._predefined_expressions = expressions_from_config( get_path(self._predefined_expressions_path) if self. _predefined_expressions_path else None) for expression in self._predefined_expressions: valid, message = validate_predefined_expression( expression, self._vector_selector_base_data) if not valid: warnings.warn(message) expression["isValid"] = valid # Add expressions to custom vector definitions self._custom_vector_definitions_base = copy.deepcopy( self._custom_vector_definitions) _custom_vector_definitions_from_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: 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) add_expressions_to_vector_selector_data( self._initial_vector_selector_data, self._predefined_expressions) plot_options = options if options else {} self._initial_visualization_selection = VisualizationOptions( plot_options.get("visualization", "statistics")) # Initial selected vectors - NB: {vector1, vector2, vector3} is deprecated! initial_vectors: List[str] = plot_options.get("vectors", []) # TODO: Remove when depretaced code is not utilized anymore if "vectors" in plot_options and any( elm in plot_options for elm in ["vector1", "vector2", "vector3"]): warnings.warn( 'Providing new user input option "vectors" and deprecated user input options ' '"vector1", "vector2" and "vector3" simultaneously. Initially selected vectors ' 'for plugin are set equal to new user input option "vectors".') if not initial_vectors: initial_vectors = [ plot_options[elm] for elm in ["vector1", "vector2", "vector3"] if elm in plot_options ][:3] # Check if initially selected vectors exist in data, raise ValueError if not missing_vectors = [ elm for elm in initial_vectors if not is_vector_name_in_vector_selector_data( elm, self._initial_vector_selector_data) ] if missing_vectors: raise ValueError( f"Cannot find: {', '.join(missing_vectors)} to plot initially in " "SimulationTimeSeries. Check that the vector(s) exist in your data." ) if len(initial_vectors) > 3: warnings.warn( 'User input option "vectors" contains more than 3 vectors. Only the first 3 listed ' "vectors are kept for initially selected vectors - the remaining are neglected." ) self._initial_vectors = initial_vectors[:3] # Set callbacks self.set_callbacks(app)
def get_non_historical_vector_names(self) -> list: return [ vector for vector in self._vector_names if historical_vector(vector, None, False) not in self._vector_names ]
def __init__( self, app: dash.Dash, webviz_settings: WebvizSettings, ensembles: Optional[list] = None, rel_file_pattern: str = "share/results/unsmry/*.arrow", perform_presampling: bool = False, obsfile: Path = None, options: dict = None, sampling: str = Frequency.MONTHLY.value, predefined_expressions: str = None, line_shape_fallback: str = "linear", ) -> None: super().__init__() # NOTE: Temporary css, pending on new wcc modal component. # See: https://github.com/equinor/webviz-core-components/issues/163 WEBVIZ_ASSETS.add( Path(webviz_subsurface.__file__).parent / "_assets" / "css" / "modal.css") self._webviz_settings = webviz_settings self._obsfile = obsfile self._line_shape_fallback = set_simulation_line_shape_fallback( line_shape_fallback) # Must define valid freqency! if Frequency.from_string_value(sampling) is None: raise ValueError( 'Sampling frequency conversion is "None", i.e. Raw sampling, and ' "is not supported by plugin yet!") self._sampling = Frequency(sampling) 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 # ensemble, etc. if ensembles is not None: ensemble_paths: Dict[str, Path] = { ensemble_name: webviz_settings.shared_settings["scratch_ensembles"] [ensemble_name] for ensemble_name in ensembles } if perform_presampling: self._presampled_frequency = self._sampling self._input_provider_set = create_presampled_provider_set_from_paths( ensemble_paths, rel_file_pattern, self._presampled_frequency) else: self._input_provider_set = create_lazy_provider_set_from_paths( ensemble_paths, rel_file_pattern) else: raise ValueError('Incorrect argument, must provide "ensembles"') if not self._input_provider_set: raise ValueError( "Initial provider set is undefined, and ensemble summary providers" " are not instanciated for plugin") self._theme = webviz_settings.theme self._observations = {} if self._obsfile: self._observations = check_and_format_observations( get_path(self._obsfile)) # NOTE: Initially keep set of all vector names - can make dynamic if wanted? vector_names = self._input_provider_set.all_vector_names() non_historical_vector_names = [ vector for vector in vector_names if historical_vector(vector, None, False) not in vector_names ] # NOTE: Initially: With set of vector names, the vector selector data is static # Can be made dynamic based on selected ensembles - i.e. vectors present among # selected providers? self._vector_selector_base_data: list = [] self._vector_calculator_data: list = [] for vector in non_historical_vector_names: split = vector.split(":") add_vector_to_vector_selector_data( self._vector_selector_base_data, vector, simulation_vector_description(split[0]), ) add_vector_to_vector_selector_data( self._vector_calculator_data, vector, simulation_vector_description(split[0]), ) metadata = (self._input_provider_set.vector_metadata(vector) if self._input_provider_set else None) 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(":") add_vector_to_vector_selector_data( self._vector_selector_base_data, avgrate_vec, f"{simulation_vector_description(avgrate_split[0])} ({avgrate_vec})", ) add_vector_to_vector_selector_data( self._vector_selector_base_data, interval_vec, f"{simulation_vector_description(interval_split[0])} ({interval_vec})", ) # Retreive predefined expressions from configuration and validate self._predefined_expressions_path = ( None if predefined_expressions is None else webviz_settings. shared_settings["predefined_expressions"][predefined_expressions]) self._predefined_expressions = expressions_from_config( get_path(self._predefined_expressions_path) if self. _predefined_expressions_path else None) for expression in self._predefined_expressions: valid, message = validate_predefined_expression( expression, self._vector_selector_base_data) if not valid: warnings.warn(message) expression["isValid"] = valid # Create initial vector selector data with predefined expressions self._initial_vector_selector_data = copy.deepcopy( self._vector_selector_base_data) add_expressions_to_vector_selector_data( self._initial_vector_selector_data, self._predefined_expressions) plot_options = options if options else {} self._initial_visualization_selection = VisualizationOptions( plot_options.get("visualization", "statistics")) self._initial_vectors: List[str] = [] if "vectors" not in plot_options: self._initial_vectors = [] for vector in [ vector for vector in ["vector1", "vector2", "vector3"] if vector in plot_options ]: self._initial_vectors.append(plot_options[vector]) self._initial_vectors = self._initial_vectors[:3] # Set callbacks self.set_callbacks(app)
def create_history_vectors_df( provider: EnsembleSummaryProvider, vector_names: List[str], resampling_frequency: Optional[Frequency], ) -> pd.DataFrame: """Get dataframe with existing historical vector data for provided vectors. The returned dataframe contains columns with name of vector and corresponding historical data `Input:` * ensemble: str - Ensemble name * vector_names: List[str] - list of vectors to get historical data for [vector1, ... , vectorN] `Output:` * dataframe with non-historical vector names in columns and their historical data in rows. `Columns` in dataframe: ["DATE", "REAL", vector1, ..., vectorN] --------------------- `NOTE:` * Raise ValueError if vector does not exist for ensemble * If historical data does not exist for provided vector, vector is excluded from the returned dataframe. * Column names are not the historical vector name, but the original vector name, i.e. `WOPTH:OP_1` data is placed in colum with name `WOPT:OP_1` """ if len(vector_names) < 1: raise ValueError("Empty list of vector names!") provider_vectors = provider.vector_names() resampling_frequency = (resampling_frequency if provider.supports_resampling() else None) # Verify for provider for elm in vector_names: if elm not in provider_vectors: raise ValueError( f'Vector "{elm}" not present among vectors for provider') # Dict with historical vector name as key, and non-historical vector name as value historical_vector_and_vector_name_dict: Dict[str, str] = {} for vector in vector_names: # TODO: Create new historical_vector according to new provider metadata? historical_vector_name = historical_vector(vector=vector, smry_meta=None) if historical_vector_name and historical_vector_name in provider.vector_names( ): historical_vector_and_vector_name_dict[ historical_vector_name] = vector # Get lowest valid realization number realization = min(provider.realizations(), default=None) if not historical_vector_and_vector_name_dict or realization is None: return pd.DataFrame() historical_vector_names = list( historical_vector_and_vector_name_dict.keys()) historical_vectors_df = provider.get_vectors_df(historical_vector_names, resampling_frequency, realizations=[realization]) return historical_vectors_df.rename( columns=historical_vector_and_vector_name_dict)