From cd36f1c12eed17f0b8b0cd32ccb8f342d7fb4cdc Mon Sep 17 00:00:00 2001 From: belthlemar Date: Fri, 17 Nov 2023 15:21:11 +0100 Subject: [PATCH] feat(binding_constraints): draft to implement matrix validation --- .../business/matrix_constants_generator.py | 46 ++++++++++++------- .../command/create_binding_constraint.py | 32 +++++++++---- .../command/update_binding_constraint.py | 4 +- .../test_matrix_constants_generator.py | 31 +++++++++++-- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/antarest/study/storage/variantstudy/business/matrix_constants_generator.py b/antarest/study/storage/variantstudy/business/matrix_constants_generator.py index d5c929e5bd..eca1425579 100644 --- a/antarest/study/storage/variantstudy/business/matrix_constants_generator.py +++ b/antarest/study/storage/variantstudy/business/matrix_constants_generator.py @@ -35,9 +35,13 @@ ONES_SCENARIO_MATRIX = "ones_scenario_matrix" # Binding constraint aliases -BINDING_CONSTRAINT_HOURLY = "empty_2nd_member_hourly" -BINDING_CONSTRAINT_DAILY = "empty_2nd_member_daily" -BINDING_CONSTRAINT_WEEKLY = "empty_2nd_member_daily" +BINDING_CONSTRAINT_HOURLY_V86 = "empty_2nd_member_hourly_v86" +BINDING_CONSTRAINT_DAILY_V86 = "empty_2nd_member_daily_v86" +BINDING_CONSTRAINT_WEEKLY_V86 = "empty_2nd_member_daily_v86" + +BINDING_CONSTRAINT_HOURLY_V87 = "empty_2nd_member_hourly_v87" +BINDING_CONSTRAINT_DAILY_V87 = "empty_2nd_member_daily_v87" +BINDING_CONSTRAINT_WEEKLY_V87 = "empty_2nd_member_daily_v87" # Short-term storage aliases ST_STORAGE_PMAX_INJECTION = ONES_SCENARIO_MATRIX @@ -89,10 +93,17 @@ def _init(self) -> None: self.hashes[MISCGEN_TS] = self.matrix_service.create(FIXED_8_COLUMNS) # Binding constraint matrices - series = matrix_constants.binding_constraint.series_before_v87 - self.hashes[BINDING_CONSTRAINT_HOURLY] = self.matrix_service.create(series.default_binding_constraint_hourly) - self.hashes[BINDING_CONSTRAINT_DAILY] = self.matrix_service.create(series.default_binding_constraint_daily) - self.hashes[BINDING_CONSTRAINT_WEEKLY] = self.matrix_service.create(series.default_binding_constraint_weekly) + # fmt:off + series_before_v87 = matrix_constants.binding_constraint.series_before_v87 + self.hashes[BINDING_CONSTRAINT_HOURLY_V86] = self.matrix_service.create(series_before_v87.default_binding_constraint_hourly) + self.hashes[BINDING_CONSTRAINT_DAILY_V86] = self.matrix_service.create(series_before_v87.default_binding_constraint_daily) + self.hashes[BINDING_CONSTRAINT_WEEKLY_V86] = self.matrix_service.create(series_before_v87.default_binding_constraint_weekly) + + series_after_v87 = matrix_constants.binding_constraint.series_after_v87 + self.hashes[BINDING_CONSTRAINT_HOURLY_V87] = self.matrix_service.create(series_after_v87.default_binding_constraint_hourly) + self.hashes[BINDING_CONSTRAINT_DAILY_V87] = self.matrix_service.create(series_after_v87.default_binding_constraint_daily) + self.hashes[BINDING_CONSTRAINT_WEEKLY_V87] = self.matrix_service.create(series_after_v87.default_binding_constraint_weekly) + # fmt:on # Some short-term storage matrices use np.ones((8760, 1)) self.hashes[ONES_SCENARIO_MATRIX] = self.matrix_service.create( @@ -151,17 +162,20 @@ def get_default_reserves(self) -> str: def get_default_miscgen(self) -> str: return MATRIX_PROTOCOL_PREFIX + self.hashes[MISCGEN_TS] - def get_binding_constraint_hourly(self) -> str: - """2D-matrix of shape (8760, 3), filled-in with zeros.""" - return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_HOURLY] + def get_binding_constraint_hourly(self, version: int) -> str: + if version < 870: + return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_HOURLY_V86] + return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_HOURLY_V87] - def get_binding_constraint_daily(self) -> str: - """2D-matrix of shape (365, 3), filled-in with zeros.""" - return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_DAILY] + def get_binding_constraint_daily(self, version: int) -> str: + if version < 870: + return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_DAILY_V86] + return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_DAILY_V87] - def get_binding_constraint_weekly(self) -> str: - """2D-matrix of shape (52, 3), filled-in with zeros.""" - return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_WEEKLY] + def get_binding_constraint_weekly(self, version: int) -> str: + if version < 870: + return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_WEEKLY_V86] + return MATRIX_PROTOCOL_PREFIX + self.hashes[BINDING_CONSTRAINT_WEEKLY_V87] def get_st_storage_pmax_injection(self) -> str: """2D-matrix of shape (8760, 1), filled-in with ones.""" diff --git a/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py b/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py index 178c918a0c..2f5eb6c364 100644 --- a/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py +++ b/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py @@ -27,13 +27,14 @@ MatrixType = List[List[MatrixData]] -def check_matrix_values(time_step: BindingConstraintFrequency, values: MatrixType) -> None: +def check_matrix_values(time_step: BindingConstraintFrequency, values: MatrixType, version: int) -> None: """ Check the binding constraint's matrix values for the specified time step. Args: time_step: The frequency of the binding constraint: "hourly", "daily" or "weekly". values: The binding constraint's 2nd member matrix. + version: Corresponds to the study version Raises: ValueError: @@ -47,8 +48,13 @@ def check_matrix_values(time_step: BindingConstraintFrequency, values: MatrixTyp } # Check the matrix values and create the corresponding matrix link array = np.array(values, dtype=np.float64) - if array.shape != shapes[time_step]: - raise ValueError(f"Invalid matrix shape {array.shape}, expected {shapes[time_step]}") + actual_shape = array.shape + expected_shape = shapes[time_step] + if version < 870: + if actual_shape != expected_shape: + raise ValueError(f"Invalid matrix shape {actual_shape}, expected {expected_shape}") + elif actual_shape[0] != expected_shape[0]: + raise ValueError(f"Invalid matrix length {actual_shape[0]}, expected {expected_shape[0]}") if np.isnan(array).any(): raise ValueError("Matrix values cannot contain NaN") @@ -67,6 +73,8 @@ class AbstractBindingConstraintCommand(ICommand, metaclass=ABCMeta): filter_year_by_year: Optional[str] = None filter_synthesis: Optional[str] = None comments: Optional[str] = None + group: Optional[str] = None + version: int = 1 def to_dto(self) -> CommandDTO: args = { @@ -77,6 +85,7 @@ def to_dto(self) -> CommandDTO: "comments": self.comments, "filter_year_by_year": self.filter_year_by_year, "filter_synthesis": self.filter_synthesis, + "group": self.group, } if self.values is not None: args["values"] = strip_matrix_protocol(self.values) @@ -113,20 +122,25 @@ def validate_series( constants: GeneratorMatrixConstants constants = values["command_context"].generator_matrix_constants time_step = values["time_step"] + # todo: change this fake version to the actual study version. I do not see a nice way to do it :/ + # Solutions i thought of : + # 1) Add the study version to the class attributes -> Solve the issue but weird and hard to do sometimes + # 2) Add the value pre=True in the validator and each time creating a class, specify the study version. + # It's also weird and hard to do ... + fake_version = 860 if v is None: # Use an already-registered default matrix methods = { - BindingConstraintFrequency.HOURLY: constants.get_binding_constraint_hourly, - BindingConstraintFrequency.DAILY: constants.get_binding_constraint_daily, - BindingConstraintFrequency.WEEKLY: constants.get_binding_constraint_weekly, + BindingConstraintFrequency.HOURLY: constants.get_binding_constraint_hourly(fake_version), + BindingConstraintFrequency.DAILY: constants.get_binding_constraint_daily(fake_version), + BindingConstraintFrequency.WEEKLY: constants.get_binding_constraint_weekly(fake_version), } - method = methods[time_step] - return method() + return methods[time_step] if isinstance(v, str): # Check the matrix link return validate_matrix(v, values) if isinstance(v, list): - check_matrix_values(time_step, v) + check_matrix_values(time_step, v, fake_version) return validate_matrix(v, values) # Invalid datatype # pragma: no cover diff --git a/antarest/study/storage/variantstudy/model/command/update_binding_constraint.py b/antarest/study/storage/variantstudy/model/command/update_binding_constraint.py index ea52dca4c4..f01fe7969d 100644 --- a/antarest/study/storage/variantstudy/model/command/update_binding_constraint.py +++ b/antarest/study/storage/variantstudy/model/command/update_binding_constraint.py @@ -52,7 +52,9 @@ def validate_series( # Check the matrix link return validate_matrix(v, values) if isinstance(v, list): - check_matrix_values(time_step, v) + # todo: change this fake version by the actual study version + fake_version = 860 + check_matrix_values(time_step, v, fake_version) return validate_matrix(v, values) # Invalid datatype # pragma: no cover diff --git a/tests/study/storage/variantstudy/business/test_matrix_constants_generator.py b/tests/study/storage/variantstudy/business/test_matrix_constants_generator.py index 93a3262259..183c71709c 100644 --- a/tests/study/storage/variantstudy/business/test_matrix_constants_generator.py +++ b/tests/study/storage/variantstudy/business/test_matrix_constants_generator.py @@ -37,21 +37,42 @@ def test_get_st_storage(self, tmp_path): matrix_dto5 = generator.matrix_service.get(matrix_id5) assert np.array(matrix_dto5.data).all() == matrix_constants.st_storage.series.inflows.all() - def test_get_binding_constraint(self, tmp_path): + def test_get_binding_constraint_before_v87(self, tmp_path): + version = 860 generator = GeneratorMatrixConstants(matrix_service=SimpleMatrixService(bucket_dir=tmp_path)) - series = matrix_constants.binding_constraint.series + series = matrix_constants.binding_constraint.series_before_v87 - hourly = generator.get_binding_constraint_hourly() + hourly = generator.get_binding_constraint_hourly(version) hourly_matrix_id = hourly.split(MATRIX_PROTOCOL_PREFIX)[1] hourly_matrix_dto = generator.matrix_service.get(hourly_matrix_id) assert np.array(hourly_matrix_dto.data).all() == series.default_binding_constraint_hourly.all() - daily = generator.get_binding_constraint_daily() + daily = generator.get_binding_constraint_daily(version) daily_matrix_id = daily.split(MATRIX_PROTOCOL_PREFIX)[1] daily_matrix_dto = generator.matrix_service.get(daily_matrix_id) assert np.array(daily_matrix_dto.data).all() == series.default_binding_constraint_daily.all() - weekly = generator.get_binding_constraint_weekly() + weekly = generator.get_binding_constraint_weekly(version) + weekly_matrix_id = weekly.split(MATRIX_PROTOCOL_PREFIX)[1] + weekly_matrix_dto = generator.matrix_service.get(weekly_matrix_id) + assert np.array(weekly_matrix_dto.data).all() == series.default_binding_constraint_weekly.all() + + def test_get_binding_constraint_after_v87(self, tmp_path): + version = 870 + generator = GeneratorMatrixConstants(matrix_service=SimpleMatrixService(bucket_dir=tmp_path)) + series = matrix_constants.binding_constraint.series_after_v87 + + hourly = generator.get_binding_constraint_hourly(version) + hourly_matrix_id = hourly.split(MATRIX_PROTOCOL_PREFIX)[1] + hourly_matrix_dto = generator.matrix_service.get(hourly_matrix_id) + assert np.array(hourly_matrix_dto.data).all() == series.default_binding_constraint_hourly.all() + + daily = generator.get_binding_constraint_daily(version) + daily_matrix_id = daily.split(MATRIX_PROTOCOL_PREFIX)[1] + daily_matrix_dto = generator.matrix_service.get(daily_matrix_id) + assert np.array(daily_matrix_dto.data).all() == series.default_binding_constraint_daily.all() + + weekly = generator.get_binding_constraint_weekly(version) weekly_matrix_id = weekly.split(MATRIX_PROTOCOL_PREFIX)[1] weekly_matrix_dto = generator.matrix_service.get(weekly_matrix_id) assert np.array(weekly_matrix_dto.data).all() == series.default_binding_constraint_weekly.all()