Skip to content

Commit

Permalink
Merge pull request #128 from Deltares/feature/DEI-237-make-water-leve…
Browse files Browse the repository at this point in the history
…l,-bottom-level-and-interfaces-remappable-and-user-defined

Feature/dei 237 make water level, bottom level and interfaces remappable and user defined
  • Loading branch information
HiddeElzinga authored Sep 6, 2024
2 parents c5eca48 + 043173d commit 5f9741c
Show file tree
Hide file tree
Showing 21 changed files with 95 additions and 263 deletions.
37 changes: 0 additions & 37 deletions decoimpact/business/entities/rule_based_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from decoimpact.business.entities.i_model import IModel, ModelStatus
from decoimpact.business.entities.rule_processor import RuleProcessor
from decoimpact.business.entities.rules.i_rule import IRule
from decoimpact.crosscutting.delft3d_specific_data import delft3d_specific_names
from decoimpact.crosscutting.i_logger import ILogger


Expand Down Expand Up @@ -157,8 +156,6 @@ def _make_output_variables_list(self) -> list:
dummy_var_name = _du.get_dummy_variable_in_ugrid(dataset)
var_list = _du.get_dependent_var_list(dataset, dummy_var_name)

self._extend_names(dummy_var_name)

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)
Expand All @@ -185,7 +182,6 @@ def _validate_mappings(self, mappings: dict[str, str], logger: ILogger) -> bool:
for ds in self._input_datasets
]
)
self._check_keys_with_suffixes(mappings, delft3d_specific_names)

valid = True

Expand Down Expand Up @@ -215,16 +211,10 @@ def _validate_mappings(self, mappings: dict[str, str], logger: ILogger) -> bool:

rule_inputs = self._get_direct_rule_inputs(rule_names)

for dataset in self._input_datasets:
dummy_var_name = _du.get_dummy_variable_in_ugrid(dataset)

# check for missing rule inputs
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())
needed_rule_inputs = _du.extend_to_full_name(
needed_rule_inputs, dummy_var_name
)
missing_rule_inputs = _lu.items_not_in(needed_rule_inputs, rule_input_vars)
if len(missing_rule_inputs) > 0:
logger.log_error(
Expand Down Expand Up @@ -252,30 +242,3 @@ def _get_direct_rule_inputs(self, rule_names) -> Dict[str, List[str]]:
)

return needed_input_per_rule

def _extend_names(self, dummy_variable_name: str):
"""Extends the names of the input variables with the dummy variable prefix
when using hardcoded Delftd3D names.
"""
for rule in self._rules:
rule.input_variable_names = _du.extend_to_full_name(
rule.input_variable_names, dummy_variable_name
)

def _check_keys_with_suffixes(self, dictionary, suffixes):
"""
Checks if any key in the dictionary ends with provided suffixes.
Raise eror if that occurs.
Args:
dictionary (dict) : The dictionary to check.
suffixes (List[str]): List of suffixes to check against.
"""
for key in dictionary:
if any(key.endswith(suffix) for suffix in suffixes):
raise ValueError(
f"Remapping variables ending with"
f" {delft3d_specific_names} is not"
f" allowed."
)
84 changes: 15 additions & 69 deletions decoimpact/business/entities/rules/depth_average_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,20 @@
Classes:
DepthAverageRule
"""
from typing import Dict, List
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.delft3d_specific_data import (
BED_LEVEL_SUFFIX,
INTERFACES_SIGMA_SUFFIX,
INTERFACES_Z_SUFFIX,
WATER_LEVEL_SUFFIX,
)
from decoimpact.crosscutting.i_logger import ILogger


class DepthAverageRule(RuleBase, IMultiArrayBasedRule):
"""Implementation for the depth average rule"""

def __init__(self, name: str, input_variable_names: List[str], layer_type: str):
super().__init__(name, input_variable_names)
self._layer_type = layer_type

# pylint: disable=too-many-locals
def execute(
self, value_arrays: Dict[str, _xr.DataArray], logger: ILogger
Expand All @@ -48,38 +38,32 @@ def execute(
"""

# 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 use the first value.
variables = next(iter(value_arrays.values()))
interface_suffix = _get_layer_suffix(self._layer_type, logger)
# but the name of the key is given by the user, and is unknown here, so use
# the ordering defined in the parser.
values_list = list(value_arrays.values())

bed_level_values = _extract_variable_based_on_suffix(
value_arrays, BED_LEVEL_SUFFIX
)
depths_interfaces = _extract_variable_based_on_suffix(
value_arrays, interface_suffix
)
water_level_values = _extract_variable_based_on_suffix(
value_arrays, WATER_LEVEL_SUFFIX
)
variable = values_list[0]
bed_level_values = values_list[1]
water_level_values = values_list[2]
depths_interfaces = values_list[3]

# 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
d for d in variable.dims if d not in water_level_values.dims
][0]
layer_len = variables[dim_layer_name].size
layer_len = variable[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"The number of interfaces should be number of layers + 1. Number of "
f"interfaces = {interfaces_len}. Number of layers = {layer_len}."
)
return variables
return variable

# Deal with open layer system at water level and bed level
depths_interfaces.values[depths_interfaces.values.argmin()] = -100000
Expand Down Expand Up @@ -108,51 +92,13 @@ def execute(
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())
# Use the NaN filtering of the variable to set the correct depth per column
layer_heights = layer_heights.where(variable.notnull())

# Calculate depth average using relative value
relative_values = variables * layer_heights
relative_values = variable * layer_heights

# Calculate average
return relative_values.sum(dim=dim_layer_name) / layer_heights.sum(
dim=dim_layer_name
)


def _extract_variable_based_on_suffix(
value_arrays: Dict[str, _xr.DataArray], suffix: str
) -> List:
"""Extract the values from the XArray dataset based on the name
suffixes by matching the name, irrespective of the dummy name prefix.
Args:
value_array (DataArray): Values
suffix (str) : Suffix of the name
Returns:
values (List[str]): Values based on prefix + suffix name
"""
return [value_arrays[name] for name in value_arrays if suffix in name][0]


def _get_layer_suffix(layer_type: str, logger: ILogger):
"""Get the interface suffix depending on whether the model is a sigma or z
layer model. Give error if the interface suffix cannot be determined.
Args:
value_array (DataArray): Values
Returns:
layer_type (str): sigma or z
"""
if layer_type.lower() == "sigma":
return INTERFACES_SIGMA_SUFFIX
if layer_type.lower() == "z":
return INTERFACES_Z_SUFFIX
logger.log_error(
f"Layer type {layer_type} unknown. Allowed layer "
"type: z or sigma. Interface "
"variable could not be determined."
)
return "_unknown"
26 changes: 0 additions & 26 deletions decoimpact/business/utils/dataset_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import xarray as _xr

import decoimpact.business.utils.list_utils as _lu
from decoimpact.crosscutting.delft3d_specific_data import delft3d_specific_names
from decoimpact.crosscutting.i_logger import ILogger


Expand Down Expand Up @@ -282,11 +281,6 @@ def create_composed_dataset(
_xr.Dataset: composed dataset (with selected variables)
"""
merged_dataset = merge_list_of_datasets(input_datasets)
dummy_variable = get_dummy_variable_in_ugrid(merged_dataset)[0]
variables_to_use = extend_to_full_name(
variables_to_use,
dummy_variable
)
cleaned_dataset = remove_all_variables_except(merged_dataset, variables_to_use)

if mapping is None or len(mapping) == 0:
Expand Down Expand Up @@ -356,23 +350,3 @@ def reduce_dataset_for_writing(

dataset = remove_all_variables_except(dataset, save_only_variables)
return dataset


def extend_to_full_name(
variables: List[str],
dummy_variable: List[str]
) -> List[str]:
"""Extend suffix names to full variables names by prepending the dummy
variable name.
Args:
variables (list[str]): List of variable names
dummy_variable (str): name of dummy variable
Returns:
list[str]: list of the extended variable names
"""
dummy_variable = dummy_variable[0]
variables = [dummy_variable + var if var in delft3d_specific_names else var
for var in variables]
return variables
3 changes: 1 addition & 2 deletions decoimpact/business/workflow/model_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
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
from decoimpact.business.entities.rules.multiply_rule import MultiplyRule
from decoimpact.business.entities.rules.options.multi_array_operation_type import (
MultiArrayOperationType,
)
from decoimpact.business.entities.rules.multiply_rule import MultiplyRule
from decoimpact.business.entities.rules.response_curve_rule import ResponseCurveRule
from decoimpact.business.entities.rules.rolling_statistics_rule import (
RollingStatisticsRule,
Expand Down Expand Up @@ -111,7 +111,6 @@ def _create_rule(rule_data: IRuleData) -> IRule:
rule = DepthAverageRule(
rule_data.name,
rule_data.input_variables,
rule_data.layer_type,
)
elif isinstance(rule_data, IFilterExtremesRuleData):
rule = FilterExtremesRule(
Expand Down
22 changes: 0 additions & 22 deletions decoimpact/crosscutting/delft3d_specific_data.py

This file was deleted.

8 changes: 2 additions & 6 deletions decoimpact/data/api/i_depth_average_rule_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,5 @@ class IDepthAverageRuleData(IRuleData, ABC):
@property
@abstractmethod
def input_variables(self) -> List[str]:
"""List with input variable name and standard depth name"""

@property
@abstractmethod
def layer_type(self) -> str:
"""Layer type of the model (z or sigma)"""
"""List with input variable name, bed level name,
water level name and interface name (z or sigma)"""
7 changes: 0 additions & 7 deletions decoimpact/data/entities/depth_average_rule_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,11 @@ def __init__(
self,
name: str,
input_variables: List[str],
layer_type: str,
):
super().__init__(name)
self._input_variables = input_variables
self._layer_type = layer_type

@property
def input_variables(self) -> List[str]:
"""List with input variables"""
return self._input_variables

@property
def layer_type(self) -> str:
"""Layer type of the model (z or sigma)"""
return self._layer_type
41 changes: 10 additions & 31 deletions decoimpact/data/parsers/parser_depth_average_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@
"""
from typing import Any, Dict, List

from decoimpact.crosscutting.delft3d_specific_data import (
BED_LEVEL_SUFFIX,
INTERFACES_SIGMA_SUFFIX,
INTERFACES_Z_SUFFIX,
WATER_LEVEL_SUFFIX,
)
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
Expand All @@ -42,41 +36,26 @@ def parse_dict(self, dictionary: Dict[str, Any], logger: ILogger) -> IRuleData:
RuleBase: Rule based on the provided data
"""
name: str = get_dict_element("name", dictionary)
layer_type: str = get_dict_element("layer_type", dictionary)
interface_suffix = _obtain_interface_suffix(layer_type)
bed_level_variable = get_dict_element("bed_level_variable", dictionary)
water_level_variable = get_dict_element("water_level_variable", dictionary)
interfaces_variable = get_dict_element("interfaces_variable", dictionary)

input_variable_names: List[str] = [
get_dict_element("input_variable", dictionary),
interface_suffix,
WATER_LEVEL_SUFFIX,
BED_LEVEL_SUFFIX,
bed_level_variable,
water_level_variable,
interfaces_variable,
]

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, layer_type)
rule_data = DepthAverageRuleData(
name,
input_variable_names,
)

rule_data.output_variable = output_variable_name
rule_data.description = description

return rule_data


def _obtain_interface_suffix(layer_type: str):
"""Obtain the interface variable based on the layer_type specified.
Give an error if layer_type is not recognised
Args:
layer_type (str): z or sigma layers
Returns:
Suffix for z or sigma layers based on Delft3D
defined suffixes
"""
if layer_type.lower() == 'z':
return INTERFACES_Z_SUFFIX
if layer_type.lower() == 'sigma':
return INTERFACES_SIGMA_SUFFIX
raise NotImplementedError(f"Layer_type '{layer_type}' is not recognized. "
f"Supported options are 'z' and 'sigma'.")
Loading

0 comments on commit 5f9741c

Please sign in to comment.