Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dei 225 filter extremes rule #122

Merged
merged 28 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
810d229
add first setup filter extremes rule
CindyvdVries Jul 30, 2024
bf63b2c
Merge branch 'main' into feature/DEI-225-filter-extremes-rule
CindyvdVries Jul 30, 2024
64b5e13
test on how to implement find_peaks method
CindyvdVries Jul 30, 2024
34cd5a2
first working prototype filter extremes rule
CindyvdVries Aug 1, 2024
57aaaf1
update working version filter extremes with distance
CindyvdVries Aug 1, 2024
dde66d7
use distance and mask from user input
CindyvdVries Aug 2, 2024
def238b
implement timeoperationsettings
CindyvdVries Aug 2, 2024
ac16137
WIP set up test
CindyvdVries Aug 5, 2024
1d02401
first Test
CindyvdVries Aug 5, 2024
a3d40a3
adding tests for extreme filter rule
CindyvdVries Aug 5, 2024
1c01c87
add tests for parser and data object
CindyvdVries Aug 5, 2024
7059c7c
update documentation
CindyvdVries Aug 5, 2024
520fdee
Merge branch 'main' into feature/DEI-225-filter-extremes-rule
CindyvdVries Aug 5, 2024
a5f702d
acceptance test, mkdocs and clean up
CindyvdVries Aug 6, 2024
1552018
update documentation
CindyvdVries Aug 6, 2024
c0f0921
pylint
CindyvdVries Aug 6, 2024
61ce875
update review part 1
CindyvdVries Aug 6, 2024
4607b6d
Merge branch 'main' into feature/DEI-225-filter-extremes-rule
CindyvdVries Aug 6, 2024
cea14d0
update review part 2
CindyvdVries Aug 6, 2024
59fe32d
clarify documentation to include local maxima and local minima
mKlapwijk Aug 7, 2024
e3690a0
change sentence for readability
mKlapwijk Aug 7, 2024
c520047
add check on extreme_type options
CindyvdVries Aug 8, 2024
f30af19
Merge branch 'main' into feature/DEI-225-filter-extremes-rule
CindyvdVries Aug 8, 2024
52f3721
import mistake
CindyvdVries Aug 8, 2024
8121c43
update test
CindyvdVries Aug 8, 2024
c918386
Update parser_filter_extremes_rule.py
CindyvdVries Aug 9, 2024
e395267
fix for lower python versions (<3.11)
CindyvdVries Aug 9, 2024
bb6ba00
and get back the str not the enumerate
CindyvdVries Aug 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion decoimpact/business/entities/rules/combine_results_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from decoimpact.business.entities.rules.i_multi_array_based_rule import (
IMultiArrayBasedRule,
)
from decoimpact.business.entities.rules.multi_array_operation_type import (
from decoimpact.business.entities.rules.options.multi_array_operation_type import (
MultiArrayOperationType,
)
from decoimpact.business.entities.rules.rule_base import RuleBase
Expand Down
27 changes: 14 additions & 13 deletions decoimpact/business/entities/rules/depth_average_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ def execute(
interface_suffix = _get_layer_suffix(self._layer_type, logger)

bed_level_values = _extract_variable_based_on_suffix(
value_arrays, BED_LEVEL_SUFFIX)
value_arrays, BED_LEVEL_SUFFIX
)
depths_interfaces = _extract_variable_based_on_suffix(
value_arrays, interface_suffix)
value_arrays, interface_suffix
)
water_level_values = _extract_variable_based_on_suffix(
value_arrays, WATER_LEVEL_SUFFIX)
value_arrays, WATER_LEVEL_SUFFIX
)

# Get the dimension names for the interfaces and for the layers
dim_interfaces_name = list(depths_interfaces.dims)[0]
Expand Down Expand Up @@ -118,9 +121,8 @@ def execute(


def _extract_variable_based_on_suffix(
value_arrays: Dict[str, _xr.DataArray],
suffix: str
) -> List:
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.

Expand All @@ -134,10 +136,7 @@ def _extract_variable_based_on_suffix(
return [value_arrays[name] for name in value_arrays if suffix in name][0]


def _get_layer_suffix(
layer_type: str,
logger: ILogger
):
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.

Expand All @@ -151,7 +150,9 @@ def _get_layer_suffix(
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.")
logger.log_error(
f"Layer type {layer_type} unknown. Allowed layer "
"type: z or sigma. Interface "
"variable could not be determined."
)
return "_unknown"
137 changes: 137 additions & 0 deletions decoimpact/business/entities/rules/filter_extremes_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# 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 FilterExtremesRule class

Classes:
FilterExtremesRule
"""

from typing import List

import xarray as _xr
import scipy as _sc
import numpy as _np

from decoimpact.business.entities.rules.i_array_based_rule import IArrayBasedRule
from decoimpact.business.entities.rules.options.options_filter_extreme_rule import (
ExtremeTypeOptions,
)
from decoimpact.business.entities.rules.rule_base import RuleBase
from decoimpact.business.entities.rules.time_operation_settings import (
TimeOperationSettings,
)
from decoimpact.business.utils.data_array_utils import get_time_dimension_name
from decoimpact.crosscutting.i_logger import ILogger
from decoimpact.data.dictionary_utils import get_dict_element


class FilterExtremesRule(RuleBase, IArrayBasedRule):
"""Implementation for the filter extremes rule"""

# pylint: disable=too-many-arguments
def __init__(
self,
name: str,
input_variable_names: List[str],
extreme_type: ExtremeTypeOptions,
distance: int,
time_scale: str,
mask: bool,
):
super().__init__(name, input_variable_names)
self._settings = TimeOperationSettings(
{"second": "s", "hour": "h", "day": "D", "month": "M", "year": "Y"}
)
self._extreme_type: ExtremeTypeOptions = extreme_type
self._distance = distance
self._settings.time_scale = time_scale
self._mask = mask

@property
def settings(self):
"""Time operation settings"""
return self._settings

@property
def extreme_type(self) -> ExtremeTypeOptions:
"""Type of extremes (peaks or troughs)"""
return self._extreme_type

@property
def distance(self) -> int:
"""Minimal distance between peaks"""
return self._distance

@property
def mask(self) -> bool:
CindyvdVries marked this conversation as resolved.
Show resolved Hide resolved
"""Return either directly the values of the filtered array or a
True/False array"""
return self._mask

def validate(self, logger: ILogger) -> bool:
"""Validates if the rule is valid

Returns:
bool: wether the rule is valid
"""
return self.settings.validate(self.name, logger)

def execute(self, value_array: _xr.DataArray, logger: ILogger) -> _xr.DataArray:
"""
Retrieve the extremes
extreme_type: Either retrieve the values at the peaks or troughs
mask: If False return the values at the peaks, otherwise return a
1 at the extreme locations.

Args:
value_array (DataArray): Values to filter at extremes

Returns:
CindyvdVries marked this conversation as resolved.
Show resolved Hide resolved
DataArray: Filtered DataArray with only the extremes remaining
CindyvdVries marked this conversation as resolved.
Show resolved Hide resolved
at all other times the values are set to NaN
"""

time_scale = get_dict_element(
self.settings.time_scale, self.settings.time_scale_mapping
)

time_dim_name = get_time_dimension_name(value_array, logger)
time = value_array.time.values
timestep = (time[-1] - time[0]) / len(time)
width_time = _np.timedelta64(self.distance, time_scale)
distance = width_time / timestep

results = _xr.apply_ufunc(
self._process_peaks,
value_array,
input_core_dims=[[time_dim_name]],
output_core_dims=[[time_dim_name]],
vectorize=True,
kwargs={
"distance": distance,
"mask": self.mask,
"extreme_type": self.extreme_type,
},
)

results = results.transpose(*value_array.dims)
return results

def _process_peaks(
self, arr: _xr.DataArray, distance: float, mask: bool, extreme_type: str
):
factor = 1
if extreme_type == "troughs":
factor = -1
peaks, _ = _sc.signal.find_peaks(factor * arr, distance=distance)
values = arr[peaks]
if mask:
values = True
new_arr = _np.full_like(arr, _np.nan, dtype=float)
new_arr[peaks] = values
return new_arr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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 ExtremeTypeOptions Class

Classes:
ExtremeTypeOptions
"""
from enum import StrEnum, auto


class ExtremeTypeOptions(StrEnum):
"""Classify the extreme type options."""

PEAKS = auto()
TROUGHS = auto()
13 changes: 12 additions & 1 deletion decoimpact/business/workflow/model_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
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.filter_extremes_rule import FilterExtremesRule
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.multi_array_operation_type import (
from decoimpact.business.entities.rules.options.multi_array_operation_type import (
MultiArrayOperationType,
)
from decoimpact.business.entities.rules.multiply_rule import MultiplyRule
Expand All @@ -41,6 +42,7 @@
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_filter_extremes_rule_data import IFilterExtremesRuleData
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
Expand Down Expand Up @@ -109,6 +111,15 @@ def _create_rule(rule_data: IRuleData) -> IRule:
rule_data.input_variables,
rule_data.layer_type,
)
elif isinstance(rule_data, IFilterExtremesRuleData):
rule = FilterExtremesRule(
rule_data.name,
rule_data.input_variables,
rule_data.extreme_type,
rule_data.distance,
rule_data.time_scale,
rule_data.mask,
)
elif isinstance(rule_data, ILayerFilterRuleData):
rule = LayerFilterRule(
rule_data.name,
Expand Down
45 changes: 45 additions & 0 deletions decoimpact/data/api/i_filter_extremes_rule_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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 IFilterExtremesRuleData interface

Interfaces:
IFilterExtremesRuleData
"""

from abc import ABC, abstractmethod
from typing import List

from decoimpact.data.api.i_rule_data import IRuleData


class IFilterExtremesRuleData(IRuleData, ABC):
"""Data for a filter extremes rule"""

@property
@abstractmethod
def input_variables(self) -> List[str]:
"""List with input variable name"""

@property
@abstractmethod
def extreme_type(self) -> str:
"""Type of extremes [peaks or throughs]"""

@property
@abstractmethod
def distance(self) -> int:
"""Property for the distance between peaks"""

@property
@abstractmethod
def time_scale(self) -> str:
"""Property for the timescale of the distance between peaks"""

@property
@abstractmethod
def mask(self) -> bool:
"""Property for mask"""
4 changes: 2 additions & 2 deletions decoimpact/data/entities/depth_average_rule_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
# 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
Module for (multiple) DepthAverageRule class

Classes:
(multiple) ClassificationRuleData
(multiple) DepthAverageRuleData

"""

Expand Down
64 changes: 64 additions & 0 deletions decoimpact/data/entities/filter_extremes_rule_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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 FilterExtremesRuleData class

Classes:
FilterExtremesRuleData

"""

from typing import List

from decoimpact.data.api.i_filter_extremes_rule_data import IFilterExtremesRuleData
from decoimpact.data.entities.rule_data import RuleData


class FilterExtremesRuleData(IFilterExtremesRuleData, RuleData):
"""Class for storing data related to filter extremes rule"""

# pylint: disable=too-many-arguments
CindyvdVries marked this conversation as resolved.
Show resolved Hide resolved
def __init__(
self,
name: str,
input_variables: List[str],
extreme_type: str,
distance: int,
time_scale: str,
mask: bool,
):
super().__init__(name)
self._input_variables = input_variables
self._extreme_type = extreme_type
self._distance = distance
self._time_scale = time_scale
self._mask = mask

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

@property
def extreme_type(self) -> str:
"""Property for the extremes type"""
return self._extreme_type

@property
def distance(self) -> int:
"""Property for the distance between peaks"""
return self._distance

@property
def time_scale(self) -> str:
"""Property for the timescale of the distance between peaks"""
return self._time_scale

@property
def mask(self) -> bool:
"""Property for mask"""
return self._mask
Loading