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 axis filter rule #88

Merged
merged 8 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 77 additions & 0 deletions decoimpact/business/entities/rules/axis_filter_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# This file is part of D-EcoImpact
# Copyright (C) 2022-2023 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
# https://github.com/Deltares/D-EcoImpact/blob/main/LICENSE.md
"""
Module for AxisFilterRule class

Classes:
AxisFilterRule
"""

from typing import List

import xarray as _xr

from decoimpact.business.entities.rules.i_array_based_rule import IArrayBasedRule
from decoimpact.business.entities.rules.rule_base import RuleBase
from decoimpact.crosscutting.i_logger import ILogger


class AxisFilterRule(RuleBase, IArrayBasedRule):

"""Implementation for the layer filter rule"""

def __init__(
self,
name: str,
input_variable_names: List[str],
layer_number: int,
axis_name: str,
output_variable_name: str = "output",
description: str = "",
):
super().__init__(name, input_variable_names, output_variable_name, description)
self._layer_number = layer_number
self._axis_name = axis_name

@property
def layer_number(self) -> int:
"""Layer number property"""
return self._layer_number

@property
def axis_name(self) -> str:
"""Layer number property"""
return self._axis_name

def execute(self, value_array: _xr.DataArray, logger: ILogger) -> _xr.DataArray:

"""Obtain a 2D layer from a 3D variable

Args:
value (float): 3D value to obtain a layer from


Returns:
float: 2D variable
"""

if self._axis_name not in value_array.dims:
message = f"""Layer name is not in dim names \
[{value_array.dims}] layer_name [{self._axis_name}]"""
logger.log_error(message)
raise IndexError(message)

if not (
self._layer_number >= 0
and self._layer_number <= len(getattr(value_array, self._axis_name))
):
message = f"""Layer number should be within range \
[0,{len(getattr(value_array, self._axis_name))}]"""
logger.log_error(message)
raise IndexError(message)

return value_array.isel({self._axis_name: self._layer_number - 1})
11 changes: 11 additions & 0 deletions decoimpact/business/workflow/model_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from decoimpact.business.entities.i_model import IModel
from decoimpact.business.entities.rule_based_model import RuleBasedModel
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.formula_rule import FormulaRule
Expand All @@ -33,6 +34,7 @@
from decoimpact.business.entities.rules.time_aggregation_rule import TimeAggregationRule
from decoimpact.business.workflow.i_model_builder import IModelBuilder
from decoimpact.crosscutting.i_logger import ILogger
from decoimpact.data.api.i_axis_filter_rule_data import IAxisFilterRuleData
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
Expand Down Expand Up @@ -98,6 +100,15 @@ def _create_rule(rule_data: IRuleData) -> IRule:
rule_data.layer_number,
rule_data.output_variable,
)

if isinstance(rule_data, IAxisFilterRuleData):
return AxisFilterRule(
rule_data.name,
[rule_data.input_variable],
rule_data.layer_number,
rule_data.axis_name,
rule_data.output_variable,
)

if isinstance(rule_data, IStepFunctionRuleData):
return StepFunctionRule(
Expand Down
37 changes: 37 additions & 0 deletions decoimpact/data/api/i_axis_filter_rule_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This file is part of D-EcoImpact
# Copyright (C) 2022-2023 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 IAxisFilterRuleData interface

Interfaces:
IAxisFilterRuleData

"""


from abc import ABC, abstractmethod

from decoimpact.data.api.i_rule_data import IRuleData


class IAxisFilterRuleData(IRuleData, ABC):
"""Data for a axis filter rule"""

@property
@abstractmethod
def input_variable(self) -> str:
"""Property for the nput variable"""

@property
@abstractmethod
def layer_number(self) -> int:
"""Property for the layer number"""

@property
@abstractmethod
def axis_name(self) -> str:
"""Property for the dim name"""
50 changes: 50 additions & 0 deletions decoimpact/data/entities/axis_filter_rule_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This file is part of D-EcoImpact
# Copyright (C) 2022-2023 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 AxisFilterRuleData class

Classes:
AxisFilterRuleData

"""

from decoimpact.data.api.i_axis_filter_rule_data import IAxisFilterRuleData
from decoimpact.data.entities.rule_data import RuleData


class AxisFilterRuleData(IAxisFilterRuleData, RuleData):
"""Class for storing data related to axis filter rule rule"""

def __init__(
self,
name: str,
layer_number: int,
axis_name: str,
input_variable: str,
output_variable: str = "output",
description: str = "",
):
super().__init__(name, output_variable, description)
self._input_variable = input_variable
self._layer_number = layer_number
self._axis_name = axis_name

@property
def input_variable(self) -> str:
"""Property for the input variable"""
return self._input_variable

@property
def layer_number(self) -> int:
"""Property for the layer number"""
return self._layer_number

@property
def axis_name(self) -> str:
"""Property for the dimension name"""
return self._axis_name

62 changes: 62 additions & 0 deletions decoimpact/data/parsers/parser_axis_filter_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# This file is part of D-EcoImpact
# Copyright (C) 2022-2023 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 ParserLayerFilterRule class

Classes:
ParserLayerFilterRule
"""
from typing import Any, Dict

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.axis_filter_rule_data import AxisFilterRuleData
from decoimpact.data.parsers.i_parser_rule_base import IParserRuleBase


class ParserAxisFilterRule(IParserRuleBase):

"""Class for creating a AxisFilterRuleData"""

@property
def rule_type_name(self) -> str:
"""Type name for the rule"""
return "axis_filter_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 = get_dict_element("name", dictionary)
description = get_dict_element("description", dictionary)
input_variable_name = get_dict_element("input_variable", dictionary)
axis_name = get_dict_element("axis_name", dictionary)
if not isinstance(axis_name, str):
message = (
"Dimension name should be a string, "
f"received a {type(axis_name)}: {axis_name}"
)
raise ValueError(message)

layer_number = get_dict_element("layer_number", dictionary)
if not isinstance(layer_number, int):
message = (
"Layer number should be an integer, "
f"received a {type(layer_number)}: {layer_number}"
)
raise ValueError(message)
output_variable_name = get_dict_element("output_variable", dictionary)

return AxisFilterRuleData(
name, layer_number, axis_name, input_variable_name,
output_variable_name, description
)
2 changes: 2 additions & 0 deletions decoimpact/data/parsers/rule_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Iterator

from decoimpact.data.parsers.i_parser_rule_base import IParserRuleBase
from decoimpact.data.parsers.parser_axis_filter_rule import ParserAxisFilterRule
from decoimpact.data.parsers.parser_classification_rule import ParserClassificationRule
from decoimpact.data.parsers.parser_combine_results_rule import ParserCombineResultsRule
from decoimpact.data.parsers.parser_formula_rule import ParserFormulaRule
Expand All @@ -39,3 +40,4 @@ def rule_parsers() -> Iterator[IParserRuleBase]:
yield ParserResponseCurveRule()
yield ParserFormulaRule()
yield ParserClassificationRule()
yield ParserAxisFilterRule()
47 changes: 47 additions & 0 deletions tests/business/entities/rules/test_axis_filter_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This file is part of D-EcoImpact
# Copyright (C) 2022-2023 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 AxisFilterRule class
"""
import pytest
import xarray as _xr
from mock import Mock

from decoimpact.business.entities.rules.axis_filter_rule import AxisFilterRule
from decoimpact.crosscutting.i_logger import ILogger


def test_create_axis_filter_rule_should_set_defaults():
"""Test creating a AxisFilterRule with defaults"""

# Arrange & Act
rule = AxisFilterRule("test", ["foo"], 3, "boo", "output")

# Assert
assert rule.name == "test"
assert rule.description == ""
assert rule.input_variable_names == ["foo"]
assert rule.output_variable_name == "output"
assert rule.layer_number == 3
assert rule.axis_name == "boo"
assert isinstance(rule, AxisFilterRule)


def test_execute_value_array_axis_filtered():
"""Test execute of layer filter rule"""
# Arrange & Act
logger = Mock(ILogger)
rule = AxisFilterRule("test", ["foo"], 1, "dim_1", "output", "description")
data = [[1, 2], [3, 4]]
value_array = _xr.DataArray(data, dims=("dim_1","dim_2"))
filtered_array = rule.execute(value_array, logger)

result_data = [1,2]
result_array = _xr.DataArray(result_data, dims=("dim_2"))

# Assert
assert _xr.testing.assert_equal(filtered_array, result_array) is None
28 changes: 28 additions & 0 deletions tests/data/entities/test_axis_filter_rule_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file is part of D-EcoImpact
# Copyright (C) 2022-2023 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 AxisFilterRuleData class
"""

from decoimpact.data.api.i_rule_data import IRuleData
from decoimpact.data.entities.axis_filter_rule_data import AxisFilterRuleData


def test_axis_filter_rule_data_creation_logic():
"""The AxisFilterRuleData should parse the provided dictionary
to correctly initialize itself during creation """

#Act
data = AxisFilterRuleData("test_name", 3, "axis_name", "input", "output", "description")


#Assert

assert isinstance(data, IRuleData)
assert data.input_variable == "input"
assert data.layer_number == 3
assert data.axis_name == "axis_name"
Loading