Skip to content

Commit

Permalink
Fix: issues with user_index
Browse files Browse the repository at this point in the history
  • Loading branch information
bzah committed May 2, 2022
1 parent cea1119 commit b74c46f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 52 deletions.
2 changes: 1 addition & 1 deletion icclim/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from icclim.models.user_index_config import UserIndexConfig
from icclim.models.user_index_dict import UserIndexDict
from icclim.pre_processing.input_parsing import read_dataset, update_to_standard_coords
from icclim.user_indices.dispatcher import CalcOperation, compute_user_index
from icclim.user_indices.calc_operation import CalcOperation, compute_user_index

log: IcclimLogger = IcclimLogger.get_instance(Verbosity.LOW)

Expand Down
1 change: 0 additions & 1 deletion icclim/models/user_index_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ class UserIndexConfig:
window_width: int | None = None
coef: float | None = None
var_type: str | None = None
da_ref: DataArray | None = None
nb_event_config: NbEventConfig | None = None
save_percentile: bool = False

Expand Down
25 changes: 12 additions & 13 deletions icclim/models/user_index_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
from typing import Literal, TypedDict

from icclim.models.user_index_config import LogicalOperationLiteral
from icclim.user_indices.dispatcher import CalcOperationLiteral
from icclim.user_indices.calc_operation import CalcOperation, CalcOperationLiteral


class UserIndexDict(TypedDict, total=False):
# TODO display optional types ( | None) in here
index_name: str
calc_operation: CalcOperationLiteral
logical_operation: LogicalOperationLiteral
thresh: str | float
link_logical_operations: Literal["and", "or"]
extreme_mode: Literal["min", "max"]
window_width: int
coef: float
date_event: bool
var_type: Literal["t", "p"]
ref_time_range: list[datetime.datetime] # length of 2
calc_operation: CalcOperationLiteral | CalcOperation
logical_operation: LogicalOperationLiteral | None
thresh: str | float | None
link_logical_operations: Literal["and", "or"] | None
extreme_mode: Literal["min", "max"] | None
window_width: int | None
coef: float | None
date_event: bool | None
var_type: Literal["t", "p"] | None
ref_time_range: list[datetime.datetime] | None # length of 2
# deprecated
indice_name: str
indice_name: str | None
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@
from icclim.models.index_config import CfVariable
from icclim.models.user_index_config import LogicalOperation
from icclim.tests.test_utils import stub_pr, stub_tas, stub_user_index
from icclim.user_indices import dispatcher
from icclim.user_indices.dispatcher import CalcOperation
from icclim.user_indices import calc_operation
from icclim.user_indices.calc_operation import (
CalcOperation,
anomaly,
compute_user_index,
count_events,
max_consecutive_event_count,
run_mean,
run_sum,
)


class Test_compute:
Expand All @@ -22,7 +30,7 @@ def test_error_bad_operation(self):
user_index.freq = Frequency.MONTH
# WHEN
with pytest.raises(InvalidIcclimArgumentError):
dispatcher.compute_user_index(user_index)
compute_user_index(user_index)

def test_simple(self):
# GIVEN
Expand All @@ -31,7 +39,7 @@ def test_simple(self):
user_index.calc_operation = "max"
user_index.freq = Frequency.MONTH
# WHEN
result = dispatcher.compute_user_index(user_index)
result = compute_user_index(user_index)
# THEN
assert result.data[0] == 1

Expand All @@ -50,7 +58,7 @@ def test_simple_percentile_pr(self):
user_index.var_type = PRECIPITATION
user_index.freq = Frequency.YEAR
# WHEN
result = dispatcher.compute_user_index(user_index)
result = compute_user_index(user_index)
# THEN
assert result.data[0] == 5

Expand All @@ -67,91 +75,104 @@ def test_simple_percentile_temp(self):
user_index.var_type = TEMPERATURE
user_index.freq = Frequency.MONTH
# WHEN
result = dispatcher.compute_user_index(user_index)
result = compute_user_index(user_index)
# THEN
assert result.data[0] == 1
assert result.data[1] == 5

@patch("icclim.models.user_index_config.UserIndexConfig")
def test_error_anomaly(self, config_mock: MagicMock):
config_mock.da_ref = None
@patch("icclim.models.index_config.CfVariable")
def test_error_anomaly(self, config_mock: MagicMock, cf_var_mock: MagicMock):
config_mock.cf_vars = [cf_var_mock]
cf_var_mock.reference_da = None
with pytest.raises(MissingIcclimInputError):
dispatcher.anomaly(config_mock)
anomaly(config_mock)

@patch("icclim.user_indices.operators.anomaly")
@patch("icclim.models.user_index_config.UserIndexConfig")
def test_success_anomaly(self, config_mock: MagicMock, op_mock: MagicMock):
dispatcher.anomaly(config_mock)
@patch("icclim.user_indices.operators.anomaly")
@patch("icclim.models.index_config.CfVariable")
def test_success_anomaly(
self, config_mock: MagicMock, op_mock: MagicMock, cf_var_mock: MagicMock
):
config_mock.cf_vars = [cf_var_mock]
cf_var_mock.reference_da = [1, 2, 3] # no-op, just need to mock a valid length
anomaly(config_mock)
op_mock.assert_called_once()

@patch("icclim.models.user_index_config.UserIndexConfig")
def test_error_run_sum(self, config_mock: MagicMock):
config_mock.extreme_mode = None
with pytest.raises(MissingIcclimInputError):
dispatcher.run_sum(config_mock)
run_sum(config_mock)
config_mock.extreme_mode = {}
config_mock.window_width = None
with pytest.raises(MissingIcclimInputError):
dispatcher.run_sum(config_mock)
run_sum(config_mock)

@patch("icclim.user_indices.operators.run_sum")
@patch("icclim.models.user_index_config.UserIndexConfig")
def test_success_run_sum(self, config_mock: MagicMock, op_mock: MagicMock):
dispatcher.run_sum(config_mock)
run_sum(config_mock)
op_mock.assert_called_once()

@patch("icclim.models.user_index_config.UserIndexConfig")
def test_error_run_mean(self, config_mock: MagicMock):
config_mock.extreme_mode = None
with pytest.raises(MissingIcclimInputError):
dispatcher.run_mean(config_mock)
run_mean(config_mock)
config_mock.extreme_mode = {}
config_mock.window_width = None
with pytest.raises(MissingIcclimInputError):
dispatcher.run_mean(config_mock)
run_mean(config_mock)

@patch("icclim.user_indices.operators.run_mean")
@patch("icclim.models.user_index_config.UserIndexConfig")
def test_success_run_mean(self, config_mock: MagicMock, op_mock: MagicMock):
dispatcher.run_mean(config_mock)
run_mean(config_mock)
op_mock.assert_called_once()

@patch("icclim.models.user_index_config.UserIndexConfig")
def test_error_max_consecutive_event_count(self, config_mock: MagicMock):
config_mock.logical_operation = None
with pytest.raises(MissingIcclimInputError):
dispatcher.max_consecutive_event_count(config_mock)
max_consecutive_event_count(config_mock)
config_mock.logical_operation = {}
config_mock.thresh = None
with pytest.raises(MissingIcclimInputError):
dispatcher.max_consecutive_event_count(config_mock)
max_consecutive_event_count(config_mock)
config_mock.logical_operation = {}
config_mock.thresh = []
with pytest.raises(InvalidIcclimArgumentError):
dispatcher.max_consecutive_event_count(config_mock)
max_consecutive_event_count(config_mock)

@patch("icclim.user_indices.operators.max_consecutive_event_count")
@patch("icclim.models.user_index_config.UserIndexConfig")
def test_success_max_consecutive_event_count(
self, config_mock: MagicMock, op_mock: MagicMock
):
dispatcher.max_consecutive_event_count(config_mock)
max_consecutive_event_count(config_mock)
op_mock.assert_called_once()

@patch("icclim.models.user_index_config.UserIndexConfig")
def test_error_count_events(self, config_mock: MagicMock):
config_mock.nb_event_config = None
with pytest.raises(MissingIcclimInputError):
dispatcher.count_events(config_mock)
count_events(config_mock)

@patch("icclim.user_indices.operators.count_events")
@patch("icclim.models.user_index_config.UserIndexConfig")
def test_success_count_events(self, config_mock: MagicMock, op_mock: MagicMock):
dispatcher.count_events(config_mock)
count_events(config_mock)
op_mock.assert_called_once()

@pytest.mark.parametrize(
"reducer", [dispatcher.sum, dispatcher.mean, dispatcher.min, dispatcher.max]
"reducer",
[
calc_operation.sum,
calc_operation.mean,
calc_operation.min,
calc_operation.max,
],
)
@patch("icclim.models.user_index_config.UserIndexConfig")
def test_error_simple_reducer(self, config_mock: MagicMock, reducer: Callable):
Expand All @@ -170,5 +191,5 @@ def test_success_simple_reducer(self, config_mock: MagicMock, reducer: str):
config_mock.cf_vars = [MagicMock()]
config_mock.thresh = 42
with patch("icclim.user_indices.operators." + reducer) as op_mock:
dispatcher.compute_user_index(config_mock)
compute_user_index(config_mock)
op_mock.assert_called_once()
14 changes: 7 additions & 7 deletions icclim/tests/test_generated_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from icclim.models.netcdf_version import NetcdfVersion
from icclim.models.quantile_interpolation import QuantileInterpolation
from icclim.tests.test_utils import stub_tas
from icclim.user_indices.dispatcher import CalcOperation
from icclim.user_indices.calc_operation import CalcOperation

DEFAULT_ARGS = dict(
in_files="pouet.nc",
Expand Down Expand Up @@ -136,7 +136,7 @@ def test_custom_index__season_slice_mode(operator, exp_y1, exp_y2):
tas.loc[{"time": "2042-01-01"}] = 303.15
tas.loc[{"time": "2042-12-01"}] = 280.15
res = icclim.custom_index(
tas,
in_files=tas,
slice_mode=["season", [12, 1]],
var_name="a_name",
user_index={
Expand All @@ -161,7 +161,7 @@ def test_custom_index__season_slice_mode(operator, exp_y1, exp_y2):
def test_custom_index_run_algos__season_slice_mode(operator, exp_y1, exp_y2):
tas = stub_tas(2.0)
res = icclim.custom_index(
tas,
in_files=tas,
slice_mode=["season", [12, 1]],
var_name="a_name",
user_index={
Expand All @@ -177,15 +177,15 @@ def test_custom_index_run_algos__season_slice_mode(operator, exp_y1, exp_y2):

def test_custom_index_anomaly__season_slice_mode():
tas = stub_tas(2.0)
tas.loc[{"time": "2042-01-01"}] = 300
tas.loc[{"time": "2045-01-01"}] = 300
res = icclim.custom_index(
tas,
in_files=tas,
slice_mode=["season", [12, 1]],
var_name="a_name",
user_index={
"index_name": "pouet",
"index_name": "anomaly",
"calc_operation": CalcOperation.ANOMALY,
"ref_time_range": [datetime(2042, 1, 1), datetime(2044, 12, 31)],
},
).compute()
np.testing.assert_almost_equal(res.pouet, 0.68939251)
np.testing.assert_almost_equal(res.anomaly, 0.96129032)
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ def compute_user_index(config: UserIndexConfig) -> DataArray:


def anomaly(config: UserIndexConfig):
if config.da_ref is None:
if (
config.cf_vars[0].reference_da is None
or len(config.cf_vars[0].reference_da) == 0
):
raise MissingIcclimInputError(
f"You must provide a in base to compute {CalcOperation.ANOMALY.value}."
f"You must provide a `ref_time_range` in user_index dictionary to compute"
f" {CalcOperation.ANOMALY.value}."
f" To be valid, it must be within the dataset time range."
)
return operators.anomaly(
da=config.cf_vars[0].study_da,
da_ref=config.da_ref,
da_ref=config.cf_vars[0].reference_da,
percent=config.is_percent,
)

Expand Down Expand Up @@ -186,7 +191,6 @@ def _check_and_get_in_base_da(config: UserIndexConfig) -> DataArray | None:


class CalcOperation(Enum):
# TODO move class to models
MAX = ("max", max)
MIN = ("min", min)
SUM = ("sum", sum)
Expand Down

0 comments on commit b74c46f

Please sign in to comment.