diff --git a/decoimpact/business/entities/rule_based_model.py b/decoimpact/business/entities/rule_based_model.py index aeb63e91..6c2bf6d1 100644 --- a/decoimpact/business/entities/rule_based_model.py +++ b/decoimpact/business/entities/rule_based_model.py @@ -12,7 +12,7 @@ """ -from typing import List, Optional +from typing import Dict, List, Optional import xarray as _xr @@ -104,9 +104,8 @@ def initialize(self, logger: ILogger) -> None: self._input_datasets, self._make_output_variables_list(), self._mappings ) self._rule_processor = RuleProcessor(self._rules, self._output_dataset) - success = self._rule_processor.initialize(logger) - if not success: + if not self._rule_processor.initialize(logger): logger.log_error("Initialization failed.") def execute(self, logger: ILogger) -> None: @@ -142,8 +141,11 @@ def _make_output_variables_list(self) -> list: var_list = _du.get_dummy_and_dependent_var_list(dataset) mapping_keys = list((self._mappings or {}).keys()) + rule_names = [rule.name for rule in self._rules] + all_inputs = self._get_direct_rule_inputs(rule_names) + all_input_variables = _lu.flatten_list(list(all_inputs.values())) - all_vars = var_list + mapping_keys + self._get_direct_rule_inputs() + all_vars = var_list + mapping_keys + all_input_variables return _lu.remove_duplicates_from_list(all_vars) def _validate_mappings(self, mappings: dict[str, str], logger: ILogger) -> bool: @@ -157,7 +159,10 @@ def _validate_mappings(self, mappings: dict[str, str], logger: ILogger) -> bool: bool: if mappings are valid """ input_vars = _lu.flatten_list( - [_du.list_vars(ds) for ds in self._input_datasets] + [ + _lu.flatten_list([_du.list_vars(ds), _du.list_coords(ds)]) + for ds in self._input_datasets + ] ) valid = True @@ -174,8 +179,7 @@ def _validate_mappings(self, mappings: dict[str, str], logger: ILogger) -> bool: valid = False # check for duplicates that will be created because of mapping - mapping_vars_created = list(mappings.values()) - duplicates_created = _lu.items_in(mapping_vars_created, input_vars) + duplicates_created = _lu.items_in(list(mappings.values()), input_vars) if len(duplicates_created) > 0: logger.log_error( @@ -185,31 +189,38 @@ def _validate_mappings(self, mappings: dict[str, str], logger: ILogger) -> bool: ) valid = False + rule_names = [rule.name for rule in self._rules] + + rule_inputs = self._get_direct_rule_inputs(rule_names) + # check for missing rule inputs - needed_rule_inputs = _lu.remove_duplicates_from_list( - self._get_direct_rule_inputs() - ) - rule_input_vars = input_vars + mapping_vars_created - missing_rule_inputs = _lu.items_not_in(needed_rule_inputs, rule_input_vars) - if len(missing_rule_inputs) > 0: - logger.log_error( - f"Missing the variables '{', '.join(missing_rule_inputs)}' that " - "are required by some rules." - ) - valid = False + for rule_name, rule_input in rule_inputs.items(): + needed_rule_inputs = _lu.remove_duplicates_from_list(rule_input) + rule_input_vars = input_vars + list(mappings.values()) + missing_rule_inputs = _lu.items_not_in(needed_rule_inputs, rule_input_vars) + if len(missing_rule_inputs) > 0: + logger.log_error( + f"Missing the variables '{', '.join(missing_rule_inputs)}' that " + f"are required by '{rule_name}'." + ) + valid = False return valid - def _get_direct_rule_inputs(self) -> List[str]: + def _get_direct_rule_inputs(self, rule_names) -> Dict[str, List[str]]: """Gets the input variables directly needed by rules from input datasets. Returns: - List[str]: + Dict[str, List[str]] """ - rule_input_vars = _lu.flatten_list( - [rule.input_variable_names for rule in self._rules] - ) + rule_input_vars = [rule.input_variable_names for rule in self._rules] rule_output_vars = [rule.output_variable_name for rule in self._rules] - return _lu.items_not_in(rule_input_vars, rule_output_vars) + needed_input_per_rule = {} + for index, inputs_per_rule in enumerate(rule_input_vars): + needed_input_per_rule[rule_names[index]] = _lu.items_not_in( + inputs_per_rule, rule_output_vars + ) + + return needed_input_per_rule diff --git a/decoimpact/business/entities/rule_processor.py b/decoimpact/business/entities/rule_processor.py index 4d13cf8f..7f25c291 100644 --- a/decoimpact/business/entities/rule_processor.py +++ b/decoimpact/business/entities/rule_processor.py @@ -16,6 +16,8 @@ import numpy as _np import xarray as _xr +import decoimpact.business.utils.dataset_utils as _du +import decoimpact.business.utils.list_utils as _lu from decoimpact.business.entities.rules.i_array_based_rule import IArrayBasedRule from decoimpact.business.entities.rules.i_cell_based_rule import ICellBasedRule @@ -63,8 +65,9 @@ def initialize(self, logger: ILogger) -> bool: """ inputs: List[str] = [] - inputs = [str(key) for key in self._input_dataset] - + inputs = _lu.flatten_list( + [_du.list_vars(self._input_dataset), _du.list_coords(self._input_dataset)] + ) tree, success = self._create_rule_sets(inputs, self._rules, [], logger) if success: self._processing_list = tree diff --git a/decoimpact/business/entities/rules/axis_filter_rule.py b/decoimpact/business/entities/rules/axis_filter_rule.py index e586d13b..0277586f 100644 --- a/decoimpact/business/entities/rules/axis_filter_rule.py +++ b/decoimpact/business/entities/rules/axis_filter_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares and D-EcoImpact contributors +# Copyright (C) 2022-2024 Stichting Deltares and D-EcoImpact contributors # This program is free software distributed under the GNU # Lesser General Public License version 2.1 # A copy of the GNU General Public License can be found at diff --git a/decoimpact/business/entities/rules/depth_average_rule.py b/decoimpact/business/entities/rules/depth_average_rule.py new file mode 100644 index 00000000..72c23279 --- /dev/null +++ b/decoimpact/business/entities/rules/depth_average_rule.py @@ -0,0 +1,108 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Module for DepthAverageRule class + +Classes: + DepthAverageRule +""" +from typing import Dict + +import xarray as _xr + +from decoimpact.business.entities.rules.i_multi_array_based_rule import ( + IMultiArrayBasedRule, +) +from decoimpact.business.entities.rules.rule_base import RuleBase +from decoimpact.crosscutting.i_logger import ILogger +from decoimpact.crosscutting.delft3d_specific_data import ( + INTERFACES_NAME, + BED_LEVEL_NAME, + WATER_LEVEL_NAME, +) + + +class DepthAverageRule(RuleBase, IMultiArrayBasedRule): + """Implementation for the depth average rule""" + + def execute( + self, value_arrays: Dict[str, _xr.DataArray], logger: ILogger + ) -> _xr.DataArray: + """Calculate depth average of assumed z-layers. + + Args: + value_array (DataArray): Values to multiply + + Returns: + DataArray: Averaged values + """ + + # The first DataArray in our value_arrays contains the values to be averaged + # but the name of the key is given by the user, and is unknown here, so + # just used the first value. + variables = next(iter(value_arrays.values())) + + # depths interfaces = borders of the layers in terms of depth + depths_interfaces = value_arrays[INTERFACES_NAME] + water_level_values = value_arrays[WATER_LEVEL_NAME] + bed_level_values = value_arrays[BED_LEVEL_NAME] + + # Get the dimension names for the interfaces and for the layers + dim_interfaces_name = list(depths_interfaces.dims)[0] + interfaces_len = depths_interfaces[dim_interfaces_name].size + + dim_layer_name = [ + d for d in variables.dims if d not in water_level_values.dims + ][0] + layer_len = variables[dim_layer_name].size + + # interface dimension should always be one larger than layer dimension + # Otherwise give an error to the user + if interfaces_len != layer_len + 1: + logger.log_error( + f"The number of interfaces should be number of layers + 1. Number of" + f"interfaces = {interfaces_len}. Number of layers = {layer_len}." + ) + return variables + + # Deal with open layer system at water level and bed level + depths_interfaces.values[depths_interfaces.values.argmin()] = -100000 + depths_interfaces.values[depths_interfaces.values.argmax()] = 100000 + + # Broadcast the depths to the dimensions of the bed levels. Then make a + # correction for the depths to the bed level, in other words all depths lower + # than the bed level will be corrected to the bed level. + depths_interfaces_broadcasted = depths_interfaces.broadcast_like( + bed_level_values + ) + + corrected_depth_bed = depths_interfaces_broadcasted.where( + bed_level_values < depths_interfaces_broadcasted, bed_level_values + ) + + # Make a similar correction for the waterlevels (first broadcast to match + # dimensions and then replace all values higher than waterlevel with + # waterlevel) + corrected_depth_bed = corrected_depth_bed.broadcast_like(water_level_values) + corrected_depth_bed = corrected_depth_bed.where( + water_level_values > corrected_depth_bed, water_level_values + ) + + # Calculate the layer heights between depths + layer_heights = corrected_depth_bed.diff(dim=dim_interfaces_name) + layer_heights = layer_heights.rename({dim_interfaces_name: dim_layer_name}) + + # Use the NaN filtering of the variables to set the correct depth per column + layer_heights = layer_heights.where(variables.notnull()) + + # Calculate depth average using relative value + relative_values = variables * layer_heights + + # Calculate average + return relative_values.sum(dim=dim_layer_name) / layer_heights.sum( + dim=dim_layer_name + ) diff --git a/decoimpact/business/entities/rules/rolling_statistics_rule.py b/decoimpact/business/entities/rules/rolling_statistics_rule.py index 0e1319a4..f0078dbb 100644 --- a/decoimpact/business/entities/rules/rolling_statistics_rule.py +++ b/decoimpact/business/entities/rules/rolling_statistics_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares and D-EcoImpact contributors +# Copyright (C) 2022-2024 Stichting Deltares and D-EcoImpact contributors # This program is free software distributed under the GNU # Lesser General Public License version 2.1 # A copy of the GNU General Public License can be found at diff --git a/decoimpact/business/utils/dataset_utils.py b/decoimpact/business/utils/dataset_utils.py index 11868518..651cd59a 100644 --- a/decoimpact/business/utils/dataset_utils.py +++ b/decoimpact/business/utils/dataset_utils.py @@ -100,6 +100,18 @@ def list_vars(dataset: _xr.Dataset) -> list[str]: return list((dataset.data_vars or {}).keys()) +def list_coords(dataset: _xr.Dataset) -> list[str]: + """List coordinates in dataset + + Args: + dataset (_xr.Dataset): Dataset to list variables from + + Returns: + list_variables + """ + return list((dataset.coords or {}).keys()) + + def copy_dataset(dataset: _xr.Dataset) -> _xr.Dataset: """Copy dataset to new dataset @@ -211,7 +223,7 @@ def get_dummy_variable_in_ugrid(dataset: _xr.Dataset) -> list: def get_dummy_and_dependent_var_list(dataset: _xr.Dataset) -> list: """Obtain the list of variables in a dataset. - The dummy variable is obtained, from which a the variables are + The dummy variable is obtained, from which the variables are recursively looked up. The dummy and dependent variables are combined in one list. This is done to support XUgrid and to prevent invalid topologies. diff --git a/decoimpact/business/workflow/model_builder.py b/decoimpact/business/workflow/model_builder.py index fd5eb6c5..0c039fdc 100644 --- a/decoimpact/business/workflow/model_builder.py +++ b/decoimpact/business/workflow/model_builder.py @@ -19,6 +19,7 @@ from decoimpact.business.entities.rules.axis_filter_rule import AxisFilterRule from decoimpact.business.entities.rules.classification_rule import ClassificationRule from decoimpact.business.entities.rules.combine_results_rule import CombineResultsRule +from decoimpact.business.entities.rules.depth_average_rule import DepthAverageRule from decoimpact.business.entities.rules.formula_rule import FormulaRule from decoimpact.business.entities.rules.i_rule import IRule from decoimpact.business.entities.rules.layer_filter_rule import LayerFilterRule @@ -39,6 +40,7 @@ from decoimpact.data.api.i_classification_rule_data import IClassificationRuleData from decoimpact.data.api.i_combine_results_rule_data import ICombineResultsRuleData from decoimpact.data.api.i_data_access_layer import IDataAccessLayer +from decoimpact.data.api.i_depth_average_rule_data import IDepthAverageRuleData from decoimpact.data.api.i_formula_rule_data import IFormulaRuleData from decoimpact.data.api.i_layer_filter_rule_data import ILayerFilterRuleData from decoimpact.data.api.i_model_data import IModelData @@ -90,6 +92,10 @@ def _set_default_fields(rule_data: IRuleData, rule: RuleBase): @staticmethod def _create_rule(rule_data: IRuleData) -> IRule: + + # from python >3.10 we can use match/case, better solution + # until then disable pylint. + # pylint: disable=too-many-branches if isinstance(rule_data, IMultiplyRuleData): rule = MultiplyRule( rule_data.name, @@ -97,6 +103,11 @@ def _create_rule(rule_data: IRuleData) -> IRule: rule_data.multipliers, rule_data.date_range, ) + elif isinstance(rule_data, IDepthAverageRuleData): + rule = DepthAverageRule( + rule_data.name, + rule_data.input_variables, + ) elif isinstance(rule_data, ILayerFilterRuleData): rule = LayerFilterRule( rule_data.name, @@ -162,5 +173,4 @@ def _create_rule(rule_data: IRuleData) -> IRule: if isinstance(rule, RuleBase): ModelBuilder._set_default_fields(rule_data, rule) - return rule diff --git a/decoimpact/crosscutting/delft3d_specific_data.py b/decoimpact/crosscutting/delft3d_specific_data.py new file mode 100644 index 00000000..3295a8ed --- /dev/null +++ b/decoimpact/crosscutting/delft3d_specific_data.py @@ -0,0 +1,13 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Configuration file for hardcoded delft3d variable names +""" + +INTERFACES_NAME = "mesh2d_interface_z" +BED_LEVEL_NAME = "mesh2d_flowelem_bl" +WATER_LEVEL_NAME = "mesh2d_s1" diff --git a/decoimpact/data/api/i_depth_average_rule_data.py b/decoimpact/data/api/i_depth_average_rule_data.py new file mode 100644 index 00000000..9272ba52 --- /dev/null +++ b/decoimpact/data/api/i_depth_average_rule_data.py @@ -0,0 +1,28 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Module for IDepthAverageRuleData interface + +Interfaces: + IDepthAverageRuleData + +""" + + +from abc import ABC, abstractmethod +from typing import List + +from decoimpact.data.api.i_rule_data import IRuleData + + +class IDepthAverageRuleData(IRuleData, ABC): + """Data for a DepthAverageRule""" + + @property + @abstractmethod + def input_variables(self) -> List[str]: + """List with input variable name and standard depth name""" diff --git a/decoimpact/data/api/i_rolling_statistics_rule_data.py b/decoimpact/data/api/i_rolling_statistics_rule_data.py index 58667713..49e916b1 100644 --- a/decoimpact/data/api/i_rolling_statistics_rule_data.py +++ b/decoimpact/data/api/i_rolling_statistics_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares and D-EcoImpact contributors +# Copyright (C) 2022-2024 Stichting Deltares and D-EcoImpact contributors # This program is free software distributed under the GNU # Lesser General Public License version 2.1 # A copy of the GNU General Public License can be found at diff --git a/decoimpact/data/entities/depth_average_rule_data.py b/decoimpact/data/entities/depth_average_rule_data.py new file mode 100644 index 00000000..99afa504 --- /dev/null +++ b/decoimpact/data/entities/depth_average_rule_data.py @@ -0,0 +1,34 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Module for (multiple) ClassificationRule class + +Classes: + (multiple) ClassificationRuleData + +""" + +from typing import List +from decoimpact.data.api.i_depth_average_rule_data import IDepthAverageRuleData +from decoimpact.data.entities.rule_data import RuleData + + +class DepthAverageRuleData(IDepthAverageRuleData, RuleData): + """Class for storing data related to depth average rule""" + + def __init__( + self, + name: str, + input_variables: List[str], + ): + super().__init__(name) + self._input_variables = input_variables + + @property + def input_variables(self) -> List[str]: + """List with input variables""" + return self._input_variables diff --git a/decoimpact/data/entities/rolling_statistics_rule_data.py b/decoimpact/data/entities/rolling_statistics_rule_data.py index 255f89d7..37250e47 100644 --- a/decoimpact/data/entities/rolling_statistics_rule_data.py +++ b/decoimpact/data/entities/rolling_statistics_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares and D-EcoImpact contributors +# Copyright (C) 2022-2024 Stichting Deltares and D-EcoImpact contributors # This program is free software distributed under the GNU # Lesser General Public License version 2.1 # A copy of the GNU General Public License can be found at diff --git a/decoimpact/data/parsers/parser_depth_average_rule.py b/decoimpact/data/parsers/parser_depth_average_rule.py new file mode 100644 index 00000000..8f7960b3 --- /dev/null +++ b/decoimpact/data/parsers/parser_depth_average_rule.py @@ -0,0 +1,58 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Module for ParserDepthAverageRule class + +Classes: + ParserDepthAverageRule +""" +from typing import Any, Dict, List + +from decoimpact.crosscutting.i_logger import ILogger +from decoimpact.data.api.i_rule_data import IRuleData +from decoimpact.data.dictionary_utils import get_dict_element +from decoimpact.data.entities.depth_average_rule_data import DepthAverageRuleData +from decoimpact.data.parsers.i_parser_rule_base import IParserRuleBase +from decoimpact.crosscutting.delft3d_specific_data import ( + INTERFACES_NAME, + BED_LEVEL_NAME, + WATER_LEVEL_NAME, +) + + +class ParserDepthAverageRule(IParserRuleBase): + """Class for creating a ParserDepthAverageRule""" + + @property + def rule_type_name(self) -> str: + """Type name for the rule""" + return "depth_average_rule" + + def parse_dict(self, dictionary: Dict[str, Any], logger: ILogger) -> IRuleData: + """Parses the provided dictionary to a IRuleData + Args: + dictionary (Dict[str, Any]): Dictionary holding the values + for making the rule + Returns: + RuleBase: Rule based on the provided data + """ + name: str = get_dict_element("name", dictionary) + input_variable_names: List[str] = [ + get_dict_element("input_variable", dictionary), + INTERFACES_NAME, + WATER_LEVEL_NAME, + BED_LEVEL_NAME, + ] + output_variable_name: str = get_dict_element("output_variable", dictionary) + description: str = get_dict_element("description", dictionary, False) or "" + + rule_data = DepthAverageRuleData(name, input_variable_names) + + rule_data.output_variable = output_variable_name + rule_data.description = description + + return rule_data diff --git a/decoimpact/data/parsers/parser_rolling_statistics_rule.py b/decoimpact/data/parsers/parser_rolling_statistics_rule.py index 47a17363..4f1424d6 100644 --- a/decoimpact/data/parsers/parser_rolling_statistics_rule.py +++ b/decoimpact/data/parsers/parser_rolling_statistics_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares and D-EcoImpact contributors +# Copyright (C) 2022-2024 Stichting Deltares and D-EcoImpact contributors # This program is free software distributed under the GNU # Lesser General Public License version 2.1 # A copy of the GNU General Public License can be found at diff --git a/decoimpact/data/parsers/rule_parsers.py b/decoimpact/data/parsers/rule_parsers.py index f345edb0..21af2d56 100644 --- a/decoimpact/data/parsers/rule_parsers.py +++ b/decoimpact/data/parsers/rule_parsers.py @@ -27,6 +27,7 @@ from decoimpact.data.parsers.parser_time_aggregation_rule import ( ParserTimeAggregationRule, ) +from decoimpact.data.parsers.parser_depth_average_rule import ParserDepthAverageRule def rule_parsers() -> Iterator[IParserRuleBase]: @@ -41,3 +42,4 @@ def rule_parsers() -> Iterator[IParserRuleBase]: yield ParserFormulaRule() yield ParserClassificationRule() yield ParserAxisFilterRule() + yield ParserDepthAverageRule() diff --git a/docs/assets/images/3_depth_average.png b/docs/assets/images/3_depth_average.png new file mode 100644 index 00000000..302629fd Binary files /dev/null and b/docs/assets/images/3_depth_average.png differ diff --git a/docs/assets/images/depth average.png b/docs/assets/images/depth average.png new file mode 100644 index 00000000..bafead8c Binary files /dev/null and b/docs/assets/images/depth average.png differ diff --git a/docs/javascripts/katex.js b/docs/javascripts/katex.js new file mode 100644 index 00000000..a9417bf6 --- /dev/null +++ b/docs/javascripts/katex.js @@ -0,0 +1,10 @@ +document$.subscribe(({ body }) => { + renderMathInElement(body, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true } + ], + }) +}) \ No newline at end of file diff --git a/docs/manual/input.md b/docs/manual/input.md index c3b96f17..88e12c8e 100644 --- a/docs/manual/input.md +++ b/docs/manual/input.md @@ -19,8 +19,8 @@ output-data: In the input data the variables that are present in the input data provided through “filename” are selected for use. It is possible to filter the input data by providing a start date or end date (format: "dd-mm-yyyy"); this is optional. The variables that are used can be selected under “variable_mapping”. Here you are also able to rename variables as the name used for storage is often cryptic. -At output data the location where the output file needs to be written can be provided through “filename”. In this output file only variables that have been used from the input data and variables that have been created in the model are stored. It is possible to reduce the file size with the optional parameter "save_only_variables", which can take the name of one or several variables. -The model needs at least one rule under “rules” to execute. +At output data the location where the output file needs to be written can be provided through “filename”. In this output file only variables that have been used from the input data and variables that have been created in the model are stored. It is possible to reduce the file size with the optional parameter "save_only_variables", which can take the name of one or several variables. +The model needs at least one rule under “rules” to execute. ``` #FORMAT @@ -48,7 +48,7 @@ version: 0.1.5 # Mapping: mesh2d_sa1 : Salinity (PSU) # mesh2d_s1 : Water level (m NAP) -# mesh2d_waterdepth : Water depth (m NAP) +# mesh2d_waterdepth : Water depth (m NAP) input-data: - dataset: filename: examples/data/FM-VZM_0000_map.nc @@ -145,7 +145,7 @@ When using the multiply rule with a start and end date (or multiple start and en ### Layer filter rule ``` -FORMAT +FORMAT - layer_filter_rule: name: description: @@ -174,7 +174,7 @@ The layer filter rule allows for the extraction of a layer from 3D variables. Th ### Time aggregation rule ``` -FORMAT +FORMAT - time_aggregation_rule: name: description: @@ -184,7 +184,7 @@ FORMAT output_variable: ``` -The time aggregation rule allows for calculating a statistical summary over the time axes of 3D and 2D variables. This could be used for calculating the maximum value over a year (e.g., for water level) or the minimum value over a month (e.g., oxygen concentration). The rule operates both on 3D variables and 2D variables as long as they have a time axis and returns a 3D or 2D result depending on input with the statistic calculated for a new time axis (e.g., year or month). +The time aggregation rule allows for calculating a statistical summary over the time axes of 3D and 2D variables. This could be used for calculating the maximum value over a year (e.g., for water level) or the minimum value over a month (e.g., oxygen concentration). The rule operates both on 3D variables and 2D variables as long as they have a time axis and returns a 3D or 2D result depending on input with the statistic calculated for a new time axis (e.g., year or month). Operations available: Add, Average, Median, Min, Max, period statistics, Stdev and Percentile(n). When using percentile, add a number for the nth percentile with brackets like this: percentile(10). Stdev calculates the standard- deviation over the time period. Under period statistics are explained further in the text. Time aggregation available: Year, Month @@ -237,7 +237,7 @@ Calculate the number of consecutive periods of dry time monthly ### Step function rule ``` -FORMAT +FORMAT - step_function_rule:: name: description: @@ -267,7 +267,7 @@ The rule needs to be applied to an existing 2D/3D variable with or without time - [ 1.2 , 3.0 ] - [ 1.3 , 4.0 ] - [ 999.0 , 4.0 ] - input_variable: salinity + input_variable: salinity output_variable: salinity_class ``` @@ -299,7 +299,7 @@ The rule needs to be applied to an existing 2D/3D variable with or without time ### Response curve rule ``` -FORMAT +FORMAT - response_curve_rule: name: description: @@ -311,14 +311,14 @@ FORMAT output_variable: ``` -The response curve rule performs a linear interpolation over the provided values of the variables of 3D and 2D variables time dependent arrays. This could be used for a fuzzy logic translation of variables into ecological responses to these variables (e.g., suitability for aquatic plants based on light availability). The rule operates both on 3D variables and 2D variables, independent of the time axes, and returns decimal or fractional values in a 3D or 2D result, either with time axis, depending on input. +The response curve rule performs a linear interpolation over the provided values of the variables of 3D and 2D variables time dependent arrays. This could be used for a fuzzy logic translation of variables into ecological responses to these variables (e.g., suitability for aquatic plants based on light availability). The rule operates both on 3D variables and 2D variables, independent of the time axes, and returns decimal or fractional values in a 3D or 2D result, either with time axis, depending on input. The rule needs to be applied to an existing 2D/3D variable with or without time axis. A new 2D/3D variable with or without time axis is created when the rule is executed. ``` -#EXAMPLE : Response of the habitat suitability of Long-leaf pond weed -# (Potamogeton nodosus) to water depth. +#EXAMPLE : Response of the habitat suitability of Long-leaf pond weed +# (Potamogeton nodosus) to water depth. # Suitable between 0.0 – 2.0 m and highly suitable between 0.5 – 1.0 m - response_curve_rule: name: HSI Pond weed water depth @@ -331,7 +331,7 @@ The rule needs to be applied to an existing 2D/3D variable with or without time - [ 1.0 , 1.0 ] - [ 2.0 , 0.0 ] - [ 999.0 , 0.0 ] - input_variable: water_depth + input_variable: water_depth output_variable: HSI_Pnodosus_water_depth ``` @@ -344,7 +344,7 @@ The rule needs to be applied to an existing 2D/3D variable with or without time ### Combine results rule ``` -FORMAT +FORMAT - combine_results_rule: name: description: @@ -353,19 +353,19 @@ FORMAT output_variable: ``` -The combine results rule combines the output of two or more variables to one output variable. The way this data is combined depends on the operation chosen. This could be used for adding mutual exclusive results (e.g., habitat suitability based on flow velocity and water depth) or asses difference between results (e.g., waterlevel and bathymetry to get the water depth).The rule operates one or multiple 3D variables or 2D variables, independent of the time axes, as long as these all have the same dimensions and returns a single 3D or 2D result, either with time axis, depending on input. +The combine results rule combines the output of two or more variables to one output variable. The way this data is combined depends on the operation chosen. This could be used for adding mutual exclusive results (e.g., habitat suitability based on flow velocity and water depth) or asses difference between results (e.g., waterlevel and bathymetry to get the water depth).The rule operates one or multiple 3D variables or 2D variables, independent of the time axes, as long as these all have the same dimensions and returns a single 3D or 2D result, either with time axis, depending on input. Operations available: Add, Subtract, Multiply, Average, Median, Min and Max The rule needs to be applied to an existing 2D/3D variables with or without time axis. A new 2D/3D variable with or without time axis is created when the rule is executed. ``` -#EXAMPLE : Calculate bathymetry over time +#EXAMPLE : Calculate bathymetry over time # This is just an example, there is a variable bed level without time (mesh2d_flowelem_bl) - combine_results_rule: name: Calculate bathymetry - description: Calculate bathymetry over time by adding water level and water depth + description: Calculate bathymetry over time by adding water level and water depth operation: subtract input_variables: ["water_level","water_depth"] output_variable: bathymetry_time @@ -380,7 +380,7 @@ The rule needs to be applied to an existing 2D/3D variables with or without time ### Formula rule ``` -FORMAT +FORMAT - formula_rule: name: description: @@ -389,24 +389,24 @@ FORMAT output_variable: ``` -With the formula based rule multiple variables can be combined in a flexible way. Operations that are supported are the standard operators. +With the formula based rule multiple variables can be combined in a flexible way. Operations that are supported are the standard operators. The rule needs to be applied to an existing 2D/3D variables with or without time axis. A new 2D/3D variable with or without time axis is created when the rule is executed. ``` -#EXAMPLE : Calculate bathymetry over time +#EXAMPLE : Calculate bathymetry over time # This is just an example, there is a variable bedlevel without time (mesh2d_flowelem_bl) - formula_rule: name: Calculate bathymetry - description: Calculate bathymetry over time by adding water level and water depth + description: Calculate bathymetry over time by adding water level and water depth formula: water_level + water_depth input_variables: ["water_level","water_depth"] output_variable: bathymetry_time ``` -A lot of operators are supported with the formula based rule. Given two variables "x" and "y", formulas can be implemented for the following operators: +A lot of operators are supported with the formula based rule. Given two variables "x" and "y", formulas can be implemented for the following operators: | **Operator** | **Name** | **Example** | |:---:|:---:|:---:| @@ -418,7 +418,7 @@ A lot of operators are supported with the formula based rule. Given two variable | ** | Exponentiation | x ** y | | // | Floor division | x // y | -When a formula results in a boolean, it will be converted to a float result. Meaning that True = 1 and False = 0. Comparison, logical, identity, identity and bitwise operators are supported: +When a formula results in a boolean, it will be converted to a float result. Meaning that True = 1 and False = 0. Comparison, logical, identity, identity and bitwise operators are supported: | **Operator** | **Name** | **Example** | |:---:|:---:|:---:| @@ -452,7 +452,7 @@ For more information on these operators click [here](https://www.w3schools.com/p ### (Multiple) Classification rule ``` -FORMAT +FORMAT - classification_rule: name: description: @@ -470,7 +470,7 @@ The rule needs to be applied to an existing 2D/3D variables with or without time Criteria ranges available are: -|**Criteria range**| **Example**|**Description**| +|**Criteria range**| **Example**|**Description**| |:---:|:---:|:---:| | "-" | "-" | Value is not applicable to category, all is allowed | | "criteria_value" | "5" | Value is exectly the criteria value (only applicable for integers) | @@ -478,14 +478,14 @@ Criteria ranges available are: | "criteria_value" | ">=1" | Value needs to larger than or equal to criteria value | | "4.0" , "-" , "-"] # too deep @@ -495,11 +495,11 @@ Criteria ranges available are: input_variables: ["MIN_water_depth_mNAP", "MAX_flow_velocity", "MAX_chloride"] output_variable: aquatic_plant_classes - + - classification_rule: name: Suitability for aquatic plants description: Derive the suitability for aquatic plants based on the classification - criteria_table: + criteria_table: - ["output", "aquatic_plant_classes"] - [ 0 , "1:4"] # not suitable - [ 1 , "5"] # suitable @@ -516,7 +516,7 @@ Criteria ranges available are: ### Rolling statistic rule ``` -FORMAT +FORMAT - rolling_statistics_rule: name: description: @@ -531,7 +531,7 @@ The rolling statistic rule allows for a rolling statistic based on the chosen op Operations available: Add, Average, Median, Min, Max, count_periods, Stdev and Percentile(n). When using percentile, add a number for the nth percentile with brackets like this: percentile(10). Time scales available: hour, day -Period can be a float or integer value. +Period can be a float or integer value. The rule needs to be applied to an existing 2D/3D variables with time axis. A new 2D/3D variable with the same time axis is created when the rule is executed. @@ -574,7 +574,7 @@ In the example shown above the stripe indicates the time period covered (4 times ### Axis filter rule ``` -FORMAT +FORMAT - axis_filter_rule: name: description: @@ -602,12 +602,111 @@ The rule needs to be applied to an existing 2D/3D variables with or without time ![Result Axis filter rule](../assets/images/3_result_axis_filter.png "Salinity(in PSU, left-hand) is subset so that only face cell 13 is left (channel entrance) reducing the data to a 2D salinity plot for multiple time steps (in PSU, right-hand) while maintaining in this case the time dimension and layer dimension (face dimension is selected upon in this example and is therefore omitted in the results). ") +### Depth average rule + +``` +FORMAT +- depth_average_rule: + name: + description: + input_variable: + output_variable: +``` + +The depth average rule allows for an averaging over depth using the weighted values according to a mesh with z-layers. The input file must include a variable called 'mesh2d_interface_z' over which the the input variable will be averaged. The input_variable will be a 2D/3D variable, with or without time axis. The output_variable has the same dimensions, excluding the dimension for the depth, as it will be represented as one averaged value per cell. + +An explanation of how the depth rule works is shown in the example below. + +![Example depth average rule](../assets/images/3_depth_average.png "An example of a simplified grid with Z-layers. This model has 6 faces, 4 layers and 2 timesteps.") + +The image shows a simplified model with the following dimensions: +- mesh2d_nFaces = 6 (number of faces) +- mesh2d_nLayers = 4 (number of layers in the z direction) +- mesh2d_nInterfaces = 5 (number of interfaces that define the depth) +- time = 2 + +Below are the variables belonging to this example: + +$$ +mesh2d\_interface\_z_{(mesh2d\_nInterfaces)} = +\begin{bmatrix} +\ 0 \\ +\ -2 \\ +\ -5 \\ +\ -6.5 \\ +\ -8.5 \\ +\end{bmatrix} +$$ + +$$ +salinity _{(time, nFaces, nLayers)}= +\begin{bmatrix} + \begin{bmatrix} + 1 & 1 & 1 & 1 & 1 & 1 \\ + 2 & 2 & 2 & 2 & 2 & 2 \\ + 3 & 3 & 3 & 3 & 3 & 3 \\ + 4 & 4 & 4 & 4 & 4 & 4 + \end{bmatrix} + \begin{bmatrix} + 1 & 1 & NaN & 1 & 1 & 1 \\ + 2 & 2 & 2 & 2 & 2 & 2 \\ + 3 & 3 & 3 & 3 & 3 & 3 \\ + 4 & 4 & 4 & 4 & 4 & 4 + \end{bmatrix} +\end{bmatrix} +$$ + +$$ +mesh2d\_s1 _{(mesh2d\_nFaces, time)} = +\begin{bmatrix} + -1.4 & 0 \\ + -1.6 & -1.6 \\ + -3 & -3 \\ + -1.4 & 3 \\ + -1.6 & -1.6 \\ + -1.6 & -1.6 +\end{bmatrix} +$$ + +$$ +mesh2d\_flowelem\_bl _{(mesh2d\_nFaces)}= +\begin{bmatrix} + -7.8 \\ -7.3 \\ -5.2 \\-9.5 \\ -7 \\ -1.6 \\ +\end{bmatrix} +$$ + +This example results in the following output_variable. + +$$ +input\_variable _{(nFaces, time)}= +\begin{bmatrix} + 2.546875 & 2.269231 \\ + 2.473684 & 2.473684 \\ + 2.090909 & 2.090909 \\ + 2.851852 & 2.2 \\ + 2.388889 & 2.388889 \\ + NaN & NaN \\ +\end{bmatrix} +$$ + + +Below is an example of an input_file for the depth average rule: + +``` +#EXAMPLE : Determine a depth average for over salinity + - depth_average_rule: + name: test depth average + description: Test depth average + input_variable: salinity + output_variable: average_salinity +``` + ##Including data from another YAML file It is possible to include data in the YAML file that originates from another file. At the moment this is only applicable to another YAML file. This can be useful for storing large classification_rule tables in a separate file (for a better overview of the work file), but this functionality is not limited to that specific rule. -Here is the original rule: +Here is the original rule: ``` #EXAMPLE : Original # This is a simplified example, only top layer of flow velocity and chloride was used and year statistics @@ -615,7 +714,7 @@ Here is the original rule: - classification_rule: name: classification for aquatic plants description: classification for aquatic plants based on water depth, flow velocity and chloride. - criteria_table: + criteria_table: - ["output", "MIN_water_depth_mNAP", "MAX_flow_velocity", "MAX_chloride"] - [ 1 , "<0.10" , "-" , "-"] # too dry - [ 2 , ">4.0" , "-" , "-"] # too deep @@ -644,4 +743,4 @@ And this is the included file from tables/aquatic_plant_criteria.yaml: - [ 3 , "-" , "-" , ">400"] # too salty - [ 4 , "-" , ">1.5" , "-"] # too fast flowing - [ 5 , "0.10:4.0" , "0.0:1.5" , "0:400"] # perfect for aquatic plants -``` \ No newline at end of file +``` diff --git a/main.py b/main.py index f011140b..d6bb6532 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/mkdocs.yml b/mkdocs.yml index dea62bc4..7b0f986d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,8 @@ plugins: markdown_extensions: - attr_list + - pymdownx.arithmatex: + generic: true repo_url: https://github.com/Deltares/D-EcoImpact repo_name: Deltares/D-EcoImpact @@ -55,6 +57,14 @@ extra: - icon: fontawesome/brands/linkedin link: https://www.linkedin.com/company/deltares/ +extra_javascript: + - javascripts/katex.js + - https://unpkg.com/katex@0/dist/katex.min.js + - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js + +extra_css: + - https://unpkg.com/katex@0/dist/katex.min.css + nav: - Home: "index.md" - Installation: "installation.md" diff --git a/pyinstaller.py b/pyinstaller.py index 14cb2333..803e3cc3 100644 --- a/pyinstaller.py +++ b/pyinstaller.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/scripts/create_nc.py b/scripts/create_nc.py new file mode 100644 index 00000000..e022207f --- /dev/null +++ b/scripts/create_nc.py @@ -0,0 +1,89 @@ +""" +This file creates a simple NetCDF containing a simplified 3D grid with +3 data variables. +""" + +import numpy as np +import xarray as xr + +# Create coordinates +mesh2d_nFaces = 3 +mesh2d_node_x = np.random.rand(mesh2d_nFaces) +mesh2d_node_y = np.random.rand(mesh2d_nFaces) +mesh2d_edge_x = np.random.rand(mesh2d_nFaces) +mesh2d_edge_y = np.random.rand(mesh2d_nFaces) +mesh2d_face_x = np.random.rand(mesh2d_nFaces) +mesh2d_face_y = np.random.rand(mesh2d_nFaces) +mesh2d_nLayers = 3 +timesteps = 10 + +data_variable = np.zeros((timesteps, mesh2d_nFaces, mesh2d_nLayers)) + +mesh2d_interface_z = np.array([0, -1, -3, -7]) +mesh2d_flowelem_bl = np.array([-7, -6, -3]) +mesh2d_s1 = np.broadcast_to(np.array([0, -0.5, -3]), (timesteps, mesh2d_nFaces)) + +data_variable_A = data_variable.copy() +data_variable_B = data_variable.copy() +data_variable_C = data_variable.copy() + +# Set different values on different levels +data_variable_A[0:6, :, 0] = np.array([11, 11, 3]) +data_variable_A[0:6, :, 1] = np.array([14, 14, 3]) +data_variable_A[0:6, :, 2] = np.array([19, 19, 3]) +data_variable_A[6:, :, 0] = np.array([5, 5, 2]) +data_variable_A[6:, :, 1] = np.array([3, 3, 2]) +data_variable_A[6:, :, 2] = np.array([1, 1, 2]) + +data_variable_B[:, :, 0] = np.array([11, 11, 11]) +data_variable_B[:, :, 1] = np.array([14, 14, 14]) +data_variable_B[:, :, 2] = np.array([19, 19, 19]) +data_variable_B[:, 1, 2] = np.array([np.NaN]) + +data_variable_C[:, :, 0] = np.array([11, 11, 11]) +data_variable_C[:, :, 1] = np.array([14, 14, 14]) +data_variable_C[:, :, 2] = np.array([19, 19, 19]) +data_variable_C[:, 1, 2] = np.array([-999]) + +# Create dataset +ds = xr.Dataset( + { + "var_3d_A": ( + ["time", "mesh2d_nFaces", "mesh2d_nLayers"], + data_variable_A, + {"location": "edge", "mesh": "mesh2d"}, + ), + "var_3d_B": ( + ["time", "mesh2d_nFaces", "mesh2d_nLayers"], + data_variable_B, + {"location": "edge", "mesh": "mesh2d"}, + ), + "var_3d_C": ( + ["time", "mesh2d_nFaces", "mesh2d_nLayers"], + data_variable_C, + {"location": "edge", "mesh": "mesh2d", "_FillValue": -999}, + ), + "mesh2d_interface_z": ( + ["mesh2d_nInterfaces"], + mesh2d_interface_z, + {"location": "edge", "mesh": "mesh2d"}, + ), + "mesh2d_flowelem_bl": ( + ["mesh2d_nFaces"], + mesh2d_flowelem_bl, + {"location": "edge", "mesh": "mesh2d"}, + ), + "mesh2d_s1": (["time", "mesh2d_nFaces"], mesh2d_s1, {"location": "edge"}), + "mesh2d": ([], 1, {"cf_role": "mesh_topology"}), + }, + coords={ + "mesh2d_node_x": np.arange(mesh2d_nFaces), + "mesh2d_node_y": np.arange(mesh2d_nFaces), + "mesh2d_edge_x": np.arange(mesh2d_nFaces), + "mesh2d_edge_y": np.arange(mesh2d_nFaces), + "mesh2d_face_x": np.arange(mesh2d_nFaces), + "mesh2d_face_y": np.arange(mesh2d_nFaces), + }, +) + +ds.to_netcdf("simple_dataset.nc") diff --git a/template_input.yaml b/template_input.yaml index 59083355..feb5b854 100644 --- a/template_input.yaml +++ b/template_input.yaml @@ -62,5 +62,11 @@ rules: input_variables: [,] output_variable: + - depth_average_rule: + name: + description: + input_variable: + output_variable: + output-data: filename: diff --git a/tests/business/entities/rules/test_axis_filter_rule.py b/tests/business/entities/rules/test_axis_filter_rule.py index a6e4b4a8..e99154f8 100644 --- a/tests/business/entities/rules/test_axis_filter_rule.py +++ b/tests/business/entities/rules/test_axis_filter_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_classification_rule.py b/tests/business/entities/rules/test_classification_rule.py index fbf44c28..3eedb86f 100644 --- a/tests/business/entities/rules/test_classification_rule.py +++ b/tests/business/entities/rules/test_classification_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_combine_results_rule.py b/tests/business/entities/rules/test_combine_results_rule.py index 3a0afa28..236d93fe 100644 --- a/tests/business/entities/rules/test_combine_results_rule.py +++ b/tests/business/entities/rules/test_combine_results_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_depth_average_rule.py b/tests/business/entities/rules/test_depth_average_rule.py new file mode 100644 index 00000000..f009c6d4 --- /dev/null +++ b/tests/business/entities/rules/test_depth_average_rule.py @@ -0,0 +1,166 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Tests for RuleBase class +""" + +from typing import List +from unittest.mock import Mock + +import numpy as _np +import pytest +import xarray as _xr +from tomlkit import value + +from decoimpact.business.entities.rules.depth_average_rule import DepthAverageRule +from decoimpact.crosscutting.i_logger import ILogger + + +def test_create_depth_average_rule_with_defaults(): + """Test creating a depth average rule with defaults""" + + # Arrange & Act + rule = DepthAverageRule("test_rule_name", ["foo", "hello"]) + + # Assert + assert isinstance(rule, DepthAverageRule) + assert rule.name == "test_rule_name" + assert rule.description == "" + assert rule.input_variable_names == ["foo", "hello"] + assert rule.output_variable_name == "output" + + +def test_no_validate_error_with_correct_rule(): + """Test a correct depth average rule validates without error""" + + # Arrange + rule = DepthAverageRule( + "test_rule_name", + ["foo", "hello"], + ) + + # Assert + assert isinstance(rule, DepthAverageRule) + + +@pytest.mark.parametrize( + "data_variable, mesh2d_interface_z, mesh2d_flowelem_bl, mesh2d_s1, result_data", + [ + [ + _np.array([[[20, 40], [91, 92]]]), + _np.array([0, -1, -2]), + _np.array([-2, -2]), + _np.array([[0, 0]]), + _np.array([[30.0, 91.5]]), + ], + [ + _np.tile(_np.arange(4, 0, -1), (2, 4, 1)), + _np.array([-10, -6, -3, -1, 0]), + _np.array([-10, -5, -10, -5]), + _np.array([[0, 0, -1.5, -1.5], [0, -6, 5, -5]]), + _np.array( + [[3.0, 2.2, 3.29411765, 2.57142857], [3.0, _np.nan, 2.33333, _np.nan]] + ), + ], + # Added this next test as to match the example in documentation + [ + _np.tile(_np.arange(4, 0, -1), (2, 6, 1)), + _np.array([-8.5, -6.5, -5, -2, 0]), + _np.array([-7.8, -7.3, -5.2, -9.5, -7, -1.6]), + _np.array( + [[-1.4, -1.6, -3, -1.4, -1.6, -1.6], [0, -1.6, -3, 3, -1.6, -1.6]] + ), + _np.array( + [ + [2.546875, 2.473684, 2.090909, 2.851852, 2.388889, _np.nan], + [2.269231, 2.473684, 2.090909, 2.2, 2.388889, _np.nan], + ] + ), + ], + ], +) +def test_depth_average_rule( + data_variable: List[float], + mesh2d_interface_z: List[float], + mesh2d_flowelem_bl: List[float], + mesh2d_s1: List[float], + result_data: List[float], +): + """Make sure the calculation of the depth average is correct. Including + differing water and bed levels.""" + logger = Mock(ILogger) + rule = DepthAverageRule( + name="test", + input_variable_names=["foo"], + ) + + # Create dataset + ds = _xr.Dataset( + { + "var_3d": (["time", "mesh2d_nFaces", "mesh2d_nLayers"], data_variable), + "mesh2d_interface_z": (["mesh2d_nInterfaces"], mesh2d_interface_z), + "mesh2d_flowelem_bl": (["mesh2d_nFaces"], mesh2d_flowelem_bl), + "mesh2d_s1": (["time", "mesh2d_nFaces"], mesh2d_s1), + } + ) + + value_arrays = { + "var_3d": ds["var_3d"], + "mesh2d_interface_z": ds["mesh2d_interface_z"], + "mesh2d_flowelem_bl": ds["mesh2d_flowelem_bl"], + "mesh2d_s1": ds["mesh2d_s1"], + } + + depth_average = rule.execute(value_arrays, logger) + + result_array = _xr.DataArray( + result_data, + dims=["time", "mesh2d_nFaces"], + ) + + assert _xr.testing.assert_allclose(depth_average, result_array, atol=1e-08) is None + + +def test_dimension_error(): + """If the number of interfaces > number of layers + 1. Give an error, no calculation is possible""" + logger = Mock(ILogger) + rule = DepthAverageRule( + name="test", + input_variable_names=["foo"], + ) + + # Create dataset + ds = _xr.Dataset( + { + "var_3d": ( + ["time", "mesh2d_nFaces", "mesh2d_nLayers"], + _np.array([[[20, 40], [91, 92]]]), + ), + "mesh2d_interface_z": ( + ["mesh2d_nInterfaces"], + _np.array([0, -1, -2, -3, -4]), + ), + "mesh2d_flowelem_bl": ( + ["mesh2d_nFaces"], + _np.array([-2, -2]), + ), + "mesh2d_s1": (["time", "mesh2d_nFaces"], _np.array([[0, 0]])), + } + ) + + value_arrays = { + "var_3d": ds["var_3d"], + "mesh2d_interface_z": ds["mesh2d_interface_z"], + "mesh2d_flowelem_bl": ds["mesh2d_flowelem_bl"], + "mesh2d_s1": ds["mesh2d_s1"], + } + + rule.execute(value_arrays, logger) + logger.log_error.assert_called_with( + "The number of interfaces should be number of layers + 1. Number of" + "interfaces = 5. Number of layers = 2." + ) diff --git a/tests/business/entities/rules/test_formula_rule.py b/tests/business/entities/rules/test_formula_rule.py index 6eac4674..44248b98 100644 --- a/tests/business/entities/rules/test_formula_rule.py +++ b/tests/business/entities/rules/test_formula_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_layer_filter_rule.py b/tests/business/entities/rules/test_layer_filter_rule.py index 06fb8fc0..3c35b9bd 100644 --- a/tests/business/entities/rules/test_layer_filter_rule.py +++ b/tests/business/entities/rules/test_layer_filter_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_multiply_rule.py b/tests/business/entities/rules/test_multiply_rule.py index 98bbb34b..4fde6182 100644 --- a/tests/business/entities/rules/test_multiply_rule.py +++ b/tests/business/entities/rules/test_multiply_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_response_curve_rule.py b/tests/business/entities/rules/test_response_curve_rule.py index ec40832e..bf910c5e 100644 --- a/tests/business/entities/rules/test_response_curve_rule.py +++ b/tests/business/entities/rules/test_response_curve_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_rolling_statistics_rule.py b/tests/business/entities/rules/test_rolling_statistics_rule.py index 8b718bb2..f4735119 100644 --- a/tests/business/entities/rules/test_rolling_statistics_rule.py +++ b/tests/business/entities/rules/test_rolling_statistics_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_rule_base.py b/tests/business/entities/rules/test_rule_base.py index c71e90aa..93c0d8e9 100644 --- a/tests/business/entities/rules/test_rule_base.py +++ b/tests/business/entities/rules/test_rule_base.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_step_function_rule.py b/tests/business/entities/rules/test_step_function_rule.py index e3bd4b02..45ffb539 100644 --- a/tests/business/entities/rules/test_step_function_rule.py +++ b/tests/business/entities/rules/test_step_function_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_string_parser_utils.py b/tests/business/entities/rules/test_string_parser_utils.py index c9915a2a..979c773f 100644 --- a/tests/business/entities/rules/test_string_parser_utils.py +++ b/tests/business/entities/rules/test_string_parser_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_time_aggregation_rule.py b/tests/business/entities/rules/test_time_aggregation_rule.py index 5d4231e1..f738a1d5 100644 --- a/tests/business/entities/rules/test_time_aggregation_rule.py +++ b/tests/business/entities/rules/test_time_aggregation_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/rules/test_time_aggregation_rule_analyze_periods.py b/tests/business/entities/rules/test_time_aggregation_rule_analyze_periods.py index 232ed5b6..5df9c3fa 100644 --- a/tests/business/entities/rules/test_time_aggregation_rule_analyze_periods.py +++ b/tests/business/entities/rules/test_time_aggregation_rule_analyze_periods.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/test_rule_based_model.py b/tests/business/entities/test_rule_based_model.py index 050eb81f..ab99e102 100644 --- a/tests/business/entities/test_rule_based_model.py +++ b/tests/business/entities/test_rule_based_model.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/entities/test_rule_processor.py b/tests/business/entities/test_rule_processor.py index c039aef3..83d47169 100644 --- a/tests/business/entities/test_rule_processor.py +++ b/tests/business/entities/test_rule_processor.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/test_application.py b/tests/business/test_application.py index 2be13a80..a1c45509 100644 --- a/tests/business/test_application.py +++ b/tests/business/test_application.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -35,7 +35,7 @@ def test_running_application(): model_data.version = [0, 0, 0] application = Application(logger, data_layer, model_builder) - application.APPLICATION_VERSION = '0.0.0' + application.APPLICATION_VERSION = "0.0.0" application.APPLICATION_VERSION_PARTS = [0, 0, 0] # Act diff --git a/tests/business/utils/test_dataset_utils.py b/tests/business/utils/test_dataset_utils.py index e755cb73..c0fa7723 100644 --- a/tests/business/utils/test_dataset_utils.py +++ b/tests/business/utils/test_dataset_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/utils/test_list_utils.py b/tests/business/utils/test_list_utils.py index 4120b0ca..bec99c74 100644 --- a/tests/business/utils/test_list_utils.py +++ b/tests/business/utils/test_list_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/utils/test_version_utils.py b/tests/business/utils/test_version_utils.py index a321d124..92fd6fb7 100644 --- a/tests/business/utils/test_version_utils.py +++ b/tests/business/utils/test_version_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/workflow/test_model_builder.py b/tests/business/workflow/test_model_builder.py index d31a82a1..7562fe00 100644 --- a/tests/business/workflow/test_model_builder.py +++ b/tests/business/workflow/test_model_builder.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/business/workflow/test_model_runner.py b/tests/business/workflow/test_model_runner.py index f3ceeaf2..2950c2d6 100644 --- a/tests/business/workflow/test_model_runner.py +++ b/tests/business/workflow/test_model_runner.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -48,11 +48,14 @@ def test_run_model_with_invalid_model_should_fail(): assert model.status == ModelStatus.FAILED -@pytest.mark.parametrize("method", [ - "initialize", - "execute", - "finalize", -]) +@pytest.mark.parametrize( + "method", + [ + "initialize", + "execute", + "finalize", + ], +) def test_run_model_with_model_throwing_exception_should_fail(method: str): """Test that model runner puts the model into the Failed state if an error occurred during the execution of the provided method""" diff --git a/tests/crosscutting/test_logger_factory.py b/tests/crosscutting/test_logger_factory.py index d1959737..bbb3dd62 100644 --- a/tests/crosscutting/test_logger_factory.py +++ b/tests/crosscutting/test_logger_factory.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/crosscutting/test_logging_logger.py b/tests/crosscutting/test_logging_logger.py index ed6cf503..a4a9ef08 100644 --- a/tests/crosscutting/test_logging_logger.py +++ b/tests/crosscutting/test_logging_logger.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -14,17 +14,18 @@ from tests.testing_utils import find_log_message_by_level -@pytest.mark.parametrize("method_name, level", [ - ("log_debug", "DEBUG"), - ("log_info", "INFO"), - ("log_warning", "WARNING"), - ("log_error", "ERROR"), -]) +@pytest.mark.parametrize( + "method_name, level", + [ + ("log_debug", "DEBUG"), + ("log_info", "INFO"), + ("log_warning", "WARNING"), + ("log_error", "ERROR"), + ], +) def test_log_message_is_passed_on_to_logger( - method_name: str, - level: str, - caplog: LogCaptureFixture - ): + method_name: str, level: str, caplog: LogCaptureFixture +): """Test format of messages logged by LoggingLogger""" # Arrange diff --git a/tests/data/entities/test_axis_filter_rule_data.py b/tests/data/entities/test_axis_filter_rule_data.py index 2817d883..721f0b9a 100644 --- a/tests/data/entities/test_axis_filter_rule_data.py +++ b/tests/data/entities/test_axis_filter_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_classification_rule_data.py b/tests/data/entities/test_classification_rule_data.py index acc6a19e..db9bee5f 100644 --- a/tests/data/entities/test_classification_rule_data.py +++ b/tests/data/entities/test_classification_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_combine_results_data.py b/tests/data/entities/test_combine_results_data.py index cbfb0c17..94998631 100644 --- a/tests/data/entities/test_combine_results_data.py +++ b/tests/data/entities/test_combine_results_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_data_access_layer.py b/tests/data/entities/test_data_access_layer.py index a7e8e0a0..334a54cf 100644 --- a/tests/data/entities/test_data_access_layer.py +++ b/tests/data/entities/test_data_access_layer.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_dataset_data.py b/tests/data/entities/test_dataset_data.py index 7a9d798c..00126c2b 100644 --- a/tests/data/entities/test_dataset_data.py +++ b/tests/data/entities/test_dataset_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -33,11 +33,10 @@ def test_dataset_data_creation_logic(): assert str(data.path).endswith("test.yaml") assert "test" in data.mapping assert data.mapping["test"] == "new" - assert data.start_date == 'None' + assert data.start_date == "None" assert data.end_date == "31-12-2020" - def test_dataset_data_time_filter(): """The DatasetData should parse the provided dictionary to correctly initialize itself during creation @@ -59,6 +58,5 @@ def test_dataset_data_time_filter(): assert isinstance(data, IDatasetData) assert str(data.path).endswith("test.yaml") assert data.start_date == "01-01-2019" - assert data.end_date == 'None' + assert data.end_date == "None" # the result 'None' should result in not filtering the data set on end date - diff --git a/tests/data/entities/test_depth_average_rule_data.py b/tests/data/entities/test_depth_average_rule_data.py new file mode 100644 index 00000000..86c32ee9 --- /dev/null +++ b/tests/data/entities/test_depth_average_rule_data.py @@ -0,0 +1,26 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Tests for DepthAverageRuleData class +""" + + +from decoimpact.data.api.i_rule_data import IRuleData +from decoimpact.data.entities.depth_average_rule_data import DepthAverageRuleData + + +def test_depth_average_rule_data_creation_logic(): + """The DepthAverageRuleData should parse the provided dictionary + to correctly initialize itself during creation""" + + # Act + data = DepthAverageRuleData("test_name", "input1") + + # Assert + + assert isinstance(data, IRuleData) + assert data.input_variables == "input1" diff --git a/tests/data/entities/test_formula_rule_data.py b/tests/data/entities/test_formula_rule_data.py index 6ecbf6c1..5de51fad 100644 --- a/tests/data/entities/test_formula_rule_data.py +++ b/tests/data/entities/test_formula_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_layer_filter_rule_data.py b/tests/data/entities/test_layer_filter_rule_data.py index 3a75e55d..e5bd82b2 100644 --- a/tests/data/entities/test_layer_filter_rule_data.py +++ b/tests/data/entities/test_layer_filter_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_model_data_builder.py b/tests/data/entities/test_model_data_builder.py index 9f257916..97cb85e7 100644 --- a/tests/data/entities/test_model_data_builder.py +++ b/tests/data/entities/test_model_data_builder.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -60,7 +60,7 @@ def test_model_data_builder_parse_dict_to_model_data(): # Act data = ModelDataBuilder(logger) - contents["version"] = '0.0.0' + contents["version"] = "0.0.0" parsed_data = data.parse_yaml_data(contents) # Assert @@ -77,7 +77,7 @@ def test_model_data_builder_gives_error_when_rule_not_defined(): # Act data = ModelDataBuilder(logger) contents["rules"][0] = {"wrong_rule": "test"} - contents["version"] = '0.0.0' + contents["version"] = "0.0.0" with pytest.raises(KeyError) as exc_info: data.parse_yaml_data(contents) diff --git a/tests/data/entities/test_multiply_rule_data.py b/tests/data/entities/test_multiply_rule_data.py index bb8d8bd7..ead5a3a8 100644 --- a/tests/data/entities/test_multiply_rule_data.py +++ b/tests/data/entities/test_multiply_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_response_curve_rule_data.py b/tests/data/entities/test_response_curve_rule_data.py index d5476385..1c966f6e 100644 --- a/tests/data/entities/test_response_curve_rule_data.py +++ b/tests/data/entities/test_response_curve_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_rolling_statistics_rule_data.py b/tests/data/entities/test_rolling_statistics_rule_data.py index 1c06f829..c8826b54 100644 --- a/tests/data/entities/test_rolling_statistics_rule_data.py +++ b/tests/data/entities/test_rolling_statistics_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_rule_data.py b/tests/data/entities/test_rule_data.py index bdc7bbc5..d97dfb50 100644 --- a/tests/data/entities/test_rule_data.py +++ b/tests/data/entities/test_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_step_function_rule_data.py b/tests/data/entities/test_step_function_rule_data.py index 5a1c9ffd..7d6d89e4 100644 --- a/tests/data/entities/test_step_function_rule_data.py +++ b/tests/data/entities/test_step_function_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_time_aggregation_rule_data.py b/tests/data/entities/test_time_aggregation_rule_data.py index ab933296..4ac7f8c5 100644 --- a/tests/data/entities/test_time_aggregation_rule_data.py +++ b/tests/data/entities/test_time_aggregation_rule_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/entities/test_yaml_model_data.py b/tests/data/entities/test_yaml_model_data.py index 7bedc001..29d72857 100644 --- a/tests/data/entities/test_yaml_model_data.py +++ b/tests/data/entities/test_yaml_model_data.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_axis_filter_rule.py b/tests/data/parsers/test_parser_axis_filter_rule.py index edc00549..a1cf980a 100644 --- a/tests/data/parsers/test_parser_axis_filter_rule.py +++ b/tests/data/parsers/test_parser_axis_filter_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -28,8 +28,8 @@ def test_parser_axis_filter_rule_creation_logic(): assert isinstance(data, IParserRuleBase) assert data.rule_type_name == "axis_filter_rule" - - + + def test_parse_dict_to__axis_rule_data_logic(): """Test if a correct dictionary is parsed into a RuleData object""" # Arrange @@ -51,8 +51,8 @@ def test_parse_dict_to__axis_rule_data_logic(): # Assert assert isinstance(parsed_dict, IRuleData) - - + + def test_parse_wrong_dict_to_axis_rule_data_logic(): """Test if an incorrect dictionary is not parsed""" # Arrange @@ -78,8 +78,8 @@ def test_parse_wrong_dict_to_axis_rule_data_logic(): # Assert expected_message = "Missing element axis_name" assert exception_raised.args[0] == expected_message - - + + def test_parse_axis_name_type(): """Test if an incorrect dictionary is not parsed""" # Arrange diff --git a/tests/data/parsers/test_parser_classification_rule.py b/tests/data/parsers/test_parser_classification_rule.py index e277f520..e6c636ac 100644 --- a/tests/data/parsers/test_parser_classification_rule.py +++ b/tests/data/parsers/test_parser_classification_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_combine_results_rule.py b/tests/data/parsers/test_parser_combine_results_rule.py index 2e4475dc..0c4d8f8e 100644 --- a/tests/data/parsers/test_parser_combine_results_rule.py +++ b/tests/data/parsers/test_parser_combine_results_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_depth_average_rule.py b/tests/data/parsers/test_parser_depth_average_rule.py new file mode 100644 index 00000000..a704bb47 --- /dev/null +++ b/tests/data/parsers/test_parser_depth_average_rule.py @@ -0,0 +1,73 @@ +# This file is part of D-EcoImpact +# Copyright (C) 2022-2024 Stichting Deltares +# This program is free software distributed under the +# GNU Affero General Public License version 3.0 +# A copy of the GNU Affero General Public License can be found at +# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md +""" +Tests for ParserDepthAverageRule class +""" + +from typing import Any, List +import pytest +from mock import Mock + +from decoimpact.crosscutting.i_logger import ILogger +from decoimpact.data.api.i_rule_data import IRuleData +from decoimpact.data.parsers.i_parser_rule_base import IParserRuleBase +from decoimpact.data.parsers.parser_depth_average_rule import ParserDepthAverageRule + + +def test_parser_depth_average_rule_creation_logic(): + """The ParserDepthAverageRule should parse the provided dictionary + to correctly initialize itself during creation""" + + # Act + data = ParserDepthAverageRule() + + # Assert + + assert isinstance(data, IParserRuleBase) + assert data.rule_type_name == "depth_average_rule" + + +def test_parse_dict_to_rule_data_logic(): + """Test if a correct dictionary is parsed into a RuleData object""" + # Arrange + contents = dict( + { + "name": "testname", + "input_variable": "input", + "output_variable": "output", + } + ) + logger = Mock(ILogger) + # Act + data = ParserDepthAverageRule() + parsed_dict = data.parse_dict(contents, logger) + + assert isinstance(parsed_dict, IRuleData) + + +def test_parse_wrong_dict_to_rule_data_logic(): + """Test if an incorrect dictionary is not parsed""" + # Arrange + contents = dict( + { + "name": "testname", + "output_variable": "output", + } + ) + logger = Mock(ILogger) + + # Act + data = ParserDepthAverageRule() + + with pytest.raises(AttributeError) as exc_info: + data.parse_dict(contents, logger) + + exception_raised = exc_info.value + + # Assert + expected_message = "Missing element input_variable" + assert exception_raised.args[0] == expected_message diff --git a/tests/data/parsers/test_parser_formula_rule.py b/tests/data/parsers/test_parser_formula_rule.py index e577d280..c74588d5 100644 --- a/tests/data/parsers/test_parser_formula_rule.py +++ b/tests/data/parsers/test_parser_formula_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_layer_filter_rule.py b/tests/data/parsers/test_parser_layer_filter_rule.py index ee1504b6..d8161e10 100644 --- a/tests/data/parsers/test_parser_layer_filter_rule.py +++ b/tests/data/parsers/test_parser_layer_filter_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_multiply_rule.py b/tests/data/parsers/test_parser_multiply_rule.py index 160e3f53..2cc490ce 100644 --- a/tests/data/parsers/test_parser_multiply_rule.py +++ b/tests/data/parsers/test_parser_multiply_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -118,8 +118,8 @@ def test_multiply_parser_with_multipliers_table_correct(): ["01-01", "15-07", [1, 100]], ["16-07", "31-12", [0]], ["16-7", "31-12", [1]], - ["1-11", "31-12", [0]] - ] + ["1-11", "31-12", [0]], + ], } ) logger = Mock(ILogger) @@ -135,27 +135,24 @@ def test_multiply_parser_with_multipliers_table_correct(): "multipliers_table, expected_message", [ ( - [ - ["date", "end_date", "multipliers"], - ["01-01", "15-07", [1, 100]] - ], - "Missing element start_date" + [["date", "end_date", "multipliers"], ["01-01", "15-07", [1, 100]]], + "Missing element start_date", ), ( [ ["start_date", "not_end_date", "multipliers"], - ["01-01", "15-07", [1, 100]] + ["01-01", "15-07", [1, 100]], ], - "Missing element end_date" + "Missing element end_date", ), ( [ ["start_date", "end_date", "something_else"], - ["01-01", "15-07", [1, 100]] + ["01-01", "15-07", [1, 100]], ], - "Missing element multipliers" + "Missing element multipliers", ), - ] + ], ) def test_multiply_parser_with_multipliers_incorrect_headers( multipliers_table: List[List[Any]], expected_message: str @@ -167,7 +164,7 @@ def test_multiply_parser_with_multipliers_incorrect_headers( "name": "testname", "input_variable": "input", "output_variable": "output", - "multipliers_table": multipliers_table + "multipliers_table": multipliers_table, } ) logger = Mock(ILogger) diff --git a/tests/data/parsers/test_parser_response_curve_rule.py b/tests/data/parsers/test_parser_response_curve_rule.py index 76e50a21..234ad6b6 100644 --- a/tests/data/parsers/test_parser_response_curve_rule.py +++ b/tests/data/parsers/test_parser_response_curve_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -24,11 +24,11 @@ def _get_example_response_curve_rule_dict(): "description": "description", "input_variable": "input", "response_table": [ - ["input", "output"], - [1, 3], - [2, 2], - [3, 0], - ], + ["input", "output"], + [1, 3], + [2, 2], + [3, 0], + ], "output_variable": "outputvar", } ) @@ -101,11 +101,11 @@ def test_parse_input_values_type(): "description": "description", "input_variable": "input", "response_table": [ - ["input", "output"], - ["a", 3], - ["b", 2], - [2, 0], - ], + ["input", "output"], + ["a", 3], + ["b", 2], + [2, 0], + ], "output_variable": "output", } ) @@ -173,11 +173,11 @@ def test_parse_response_table_columns(): "description": "description", "input_variable": "input", "response_table": [ - ["input", "output", "extra"], - [1, 4, 7], - [2, 5, 8], - [3, 6, 9], - ], + ["input", "output", "extra"], + [1, 4, 7], + [2, 5, 8], + [3, 6, 9], + ], "output_variable": "output", } ) @@ -191,7 +191,5 @@ def test_parse_response_table_columns(): exception_raised = exc_info.value # Assert - expected_message = ( - "ERROR: response table should have exactly 2 columns" - ) + expected_message = "ERROR: response table should have exactly 2 columns" assert exception_raised.args[0] == expected_message diff --git a/tests/data/parsers/test_parser_rolling_statistics_rule.py b/tests/data/parsers/test_parser_rolling_statistics_rule.py index 2d33a417..df17ef5a 100644 --- a/tests/data/parsers/test_parser_rolling_statistics_rule.py +++ b/tests/data/parsers/test_parser_rolling_statistics_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_step_function_rule.py b/tests/data/parsers/test_parser_step_function_rule.py index 866102ff..cc462d15 100644 --- a/tests/data/parsers/test_parser_step_function_rule.py +++ b/tests/data/parsers/test_parser_step_function_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_parser_time_aggregation_rule.py b/tests/data/parsers/test_parser_time_aggregation_rule.py index d84a88cd..41ab2112 100644 --- a/tests/data/parsers/test_parser_time_aggregation_rule.py +++ b/tests/data/parsers/test_parser_time_aggregation_rule.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at diff --git a/tests/data/parsers/test_validation_utils.py b/tests/data/parsers/test_validation_utils.py index 2208073a..424a9d45 100644 --- a/tests/data/parsers/test_validation_utils.py +++ b/tests/data/parsers/test_validation_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -16,7 +16,7 @@ validate_all_instances_number, validate_start_before_end, validate_table_with_input, - validate_type_date + validate_type_date, ) @@ -155,11 +155,7 @@ def test_validate_table_with_input_correct(): """Test if all headers of table matches the list of input variable names""" # Arrange - test_table: Dict[str, Any] = { - "a": 1, - "b": 2, - "output": 3 - } + test_table: Dict[str, Any] = {"a": 1, "b": 2, "output": 3} test_inputs: List[str] = ["a", "b"] # Assert @@ -170,11 +166,7 @@ def test_validate_table_with_input_incorrect(): """Test if all headers of table matches the list of input variable names""" # Arrange - test_table: Dict[str, Any] = { - "a": 1, - "b": 2, - "output": 3 - } + test_table: Dict[str, Any] = {"a": 1, "b": 2, "output": 3} test_inputs: List[str] = ["a", "c"] headers = list(test_table.keys()) difference = list(set(headers) - set(test_inputs)) @@ -197,11 +189,7 @@ def test_validate_table_with_input_incorrect_output(): """Test if all headers of table matches the list of input variable names""" # Arrange - test_table: Dict[str, Any] = { - "a": 1, - "b": 2, - "out": 3 - } + test_table: Dict[str, Any] = {"a": 1, "b": 2, "out": 3} test_inputs: List[str] = ["a", "b"] # Act @@ -211,5 +199,6 @@ def test_validate_table_with_input_incorrect_output(): exception_raised = exc_info.value # Assert - assert exception_raised.args[0] == "Define an output column with the header 'output'." - + assert ( + exception_raised.args[0] == "Define an output column with the header 'output'." + ) diff --git a/tests/data/test_dictionary_utils.py b/tests/data/test_dictionary_utils.py index 1dbce1ea..c210260e 100644 --- a/tests/data/test_dictionary_utils.py +++ b/tests/data/test_dictionary_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -117,7 +117,11 @@ def test_table_lentgh_all_rows(): """Test if all rows have the same lenght.""" # Arrange - test_list: List = [["header1", "header2", "header1"], ["val1", "val2", "val4"], ["val1", "val2"]] + test_list: List = [ + ["header1", "header2", "header1"], + ["val1", "val2", "val4"], + ["val1", "val2"], + ] # Act with pytest.raises(ValueError) as exc_info: diff --git a/tests/testing_utils.py b/tests/testing_utils.py index ee132085..12fd398a 100644 --- a/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at @@ -14,8 +14,7 @@ from pytest import LogCaptureFixture -def find_log_message_by_level( - captured_log: LogCaptureFixture, level: str) -> LogRecord: +def find_log_message_by_level(captured_log: LogCaptureFixture, level: str) -> LogRecord: """Finds the correct record from the captured_log using the provided level Only one message is expected to be found @@ -29,8 +28,7 @@ def find_log_message_by_level( LogRecord: found record for the provided log level """ - records = list( - filter(lambda r: r.levelname == level, captured_log.records)) + records = list(filter(lambda r: r.levelname == level, captured_log.records)) # expect only one message for the provided level assert len(records) == 1 @@ -44,5 +42,5 @@ def get_test_data_path() -> str: Returns: str: path to the default test data folder """ - test_info: str = getenv('PYTEST_CURRENT_TEST', "") + test_info: str = getenv("PYTEST_CURRENT_TEST", "") return test_info.split(".py::")[0] + "_data" diff --git a/tests_acceptance/input_nc_files/test_depth_average_rule.nc b/tests_acceptance/input_nc_files/test_depth_average_rule.nc new file mode 100644 index 00000000..e60a0353 Binary files /dev/null and b/tests_acceptance/input_nc_files/test_depth_average_rule.nc differ diff --git a/tests_acceptance/input_yaml_files/test10d_formula_based_timeaxes_calculation.yaml b/tests_acceptance/input_yaml_files/test10d_formula_based_timeaxes_calculation.yaml index 4a057307..b409d392 100644 --- a/tests_acceptance/input_yaml_files/test10d_formula_based_timeaxes_calculation.yaml +++ b/tests_acceptance/input_yaml_files/test10d_formula_based_timeaxes_calculation.yaml @@ -9,11 +9,11 @@ input-data: rules: - formula_rule: - name: Get water_depth by caculation - description: Get water_depth by caculation + name: Get water_depth by calculation + description: Get water_depth by calculation formula: bathymetry + water_level - input_variables: [water_level, bathymetry] + input_variables: [water_level, bathymetry] output_variable: water_depth_calc - + output-data: filename: ./tests_acceptance/output_nc_files/test10d_formula_based_timeaxes_calculation.nc diff --git a/tests_acceptance/input_yaml_files/test17_depth_average_rule.yaml b/tests_acceptance/input_yaml_files/test17_depth_average_rule.yaml new file mode 100644 index 00000000..8819e01a --- /dev/null +++ b/tests_acceptance/input_yaml_files/test17_depth_average_rule.yaml @@ -0,0 +1,27 @@ +version: 0.0.0 + +input-data: + - dataset: + filename: ./tests_acceptance/input_nc_files/test_depth_average_rule.nc + +rules: + - depth_average_rule: + name: Test A + description: Test A + input_variable: var_3d_A + output_variable: output_A + + - depth_average_rule: + name: Test B + description: Test B + input_variable: var_3d_B + output_variable: output_B + + - depth_average_rule: + name: Test C + description: Test C + input_variable: var_3d_C + output_variable: output_C + +output-data: + filename: ./tests_acceptance/output_nc_files/test17_depth_average_rule.nc diff --git a/tests_acceptance/reference_nc_files/test17_depth_average_rule.nc b/tests_acceptance/reference_nc_files/test17_depth_average_rule.nc new file mode 100644 index 00000000..704bf7c6 Binary files /dev/null and b/tests_acceptance/reference_nc_files/test17_depth_average_rule.nc differ diff --git a/tests_acceptance/test_main.py b/tests_acceptance/test_main.py index 9a2ef0fb..46b05288 100644 --- a/tests_acceptance/test_main.py +++ b/tests_acceptance/test_main.py @@ -1,5 +1,5 @@ # This file is part of D-EcoImpact -# Copyright (C) 2022-2023 Stichting Deltares +# Copyright (C) 2022-2024 Stichting Deltares # This program is free software distributed under the # GNU Affero General Public License version 3.0 # A copy of the GNU Affero General Public License can be found at