Skip to content

Commit

Permalink
feat(local): implement some binding_constraints methods (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBelthle authored Mar 5, 2025
1 parent 6778b42 commit 92ca409
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 74 deletions.
3 changes: 2 additions & 1 deletion src/antares/craft/model/binding_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,10 @@ def update_term(self, term: ConstraintTermUpdate) -> None:
new_term = self._binding_constraint_service.update_binding_constraint_term(self.id, term, existing_term)
self._terms[term.id] = new_term

def update_properties(self, properties: BindingConstraintPropertiesUpdate) -> None:
def update_properties(self, properties: BindingConstraintPropertiesUpdate) -> BindingConstraintProperties:
new_properties = self._binding_constraint_service.update_binding_constraint_properties(self, properties)
self._properties = new_properties
return new_properties

def get_less_term_matrix(self) -> pd.DataFrame:
return self._binding_constraint_service.get_constraint_matrix(self, ConstraintMatrixName.LESS_TERM)
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def delete_binding_constraint(self, constraint: BindingConstraint) -> None:
self._binding_constraints.pop(constraint.id)

def update_multiple_binding_constraints(self, new_properties: Dict[str, BindingConstraintPropertiesUpdate]) -> None:
new_bc_props = self._study_service.update_multiple_binding_constraints(new_properties)
new_bc_props = self._binding_constraints_service.update_multiple_binding_constraints(new_properties)
for bc_props in new_bc_props:
self._binding_constraints[bc_props]._properties = new_bc_props[bc_props]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from antares.craft.exceptions.exceptions import (
APIError,
BindingConstraintCreationError,
BindingConstraintsUpdateError,
ConstraintMatrixDownloadError,
ConstraintMatrixUpdateError,
ConstraintPropertiesUpdateError,
Expand Down Expand Up @@ -222,3 +223,29 @@ def read_binding_constraints(self) -> list[BindingConstraint]:
return constraints
except APIError as e:
raise ConstraintRetrievalError(self.study_id, e.message) from e

@override
def update_multiple_binding_constraints(
self, new_properties: dict[str, BindingConstraintPropertiesUpdate]
) -> dict[str, BindingConstraintProperties]:
url = f"{self._base_url}/studies/{self.study_id}/table-mode/binding-constraints"
body = {}
updated_constraints: dict[str, BindingConstraintProperties] = {}

for bc_id, props in new_properties.items():
api_properties = BindingConstraintPropertiesAPI.from_user_model(props)
api_dict = api_properties.model_dump(mode="json", by_alias=True, exclude_none=True)
body[bc_id] = api_dict

try:
binding_constraints_dict = self._wrapper.put(url, json=body).json()

for binding_constraint, props in binding_constraints_dict.items():
api_response = BindingConstraintPropertiesAPI.model_validate(props)
constraints_properties = api_response.to_user_model()
updated_constraints[binding_constraint] = constraints_properties

except APIError as e:
raise BindingConstraintsUpdateError(self.study_id, e.message) from e

return updated_constraints
30 changes: 0 additions & 30 deletions src/antares/craft/service/api_services/services/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from antares.craft.exceptions.exceptions import (
APIError,
BindingConstraintDeletionError,
BindingConstraintsUpdateError,
OutputDeletionError,
OutputsRetrievalError,
StudyDeletionError,
Expand All @@ -29,11 +28,8 @@
)
from antares.craft.model.binding_constraint import (
BindingConstraint,
BindingConstraintProperties,
BindingConstraintPropertiesUpdate,
)
from antares.craft.model.output import Output
from antares.craft.service.api_services.models.binding_constraint import BindingConstraintPropertiesAPI
from antares.craft.service.api_services.utils import wait_task_completion
from antares.craft.service.base_services import BaseOutputService, BaseStudyService
from typing_extensions import override
Expand Down Expand Up @@ -151,29 +147,3 @@ def generate_thermal_timeseries(self, nb_years: int) -> None:
wait_task_completion(self._base_url, self._wrapper, task_id)
except (APIError, TaskFailedError, TaskTimeOutError) as e:
raise ThermalTimeseriesGenerationError(self.study_id, e.message)

@override
def update_multiple_binding_constraints(
self, new_properties: dict[str, BindingConstraintPropertiesUpdate]
) -> dict[str, BindingConstraintProperties]:
url = f"{self._base_url}/studies/{self.study_id}/table-mode/binding-constraints"
body = {}
updated_constraints: dict[str, BindingConstraintProperties] = {}

for bc_id, props in new_properties.items():
api_properties = BindingConstraintPropertiesAPI.from_user_model(props)
api_dict = api_properties.model_dump(mode="json", by_alias=True, exclude_none=True)
body[bc_id] = api_dict

try:
binding_constraints_dict = self._wrapper.put(url, json=body).json()

for binding_constraint, props in binding_constraints_dict.items():
api_response = BindingConstraintPropertiesAPI.model_validate(props)
constraints_properties = api_response.to_user_model()
updated_constraints[binding_constraint] = constraints_properties

except APIError as e:
raise BindingConstraintsUpdateError(self.study_id, e.message) from e

return updated_constraints
12 changes: 6 additions & 6 deletions src/antares/craft/service/base_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,12 @@ def read_binding_constraints(self) -> list["BindingConstraint"]:
"""
pass

@abstractmethod
def update_multiple_binding_constraints(
self, new_properties: Dict[str, "BindingConstraintPropertiesUpdate"]
) -> Dict[str, "BindingConstraintProperties"]:
pass


class BaseStudyService(ABC):
@property
Expand Down Expand Up @@ -642,12 +648,6 @@ def create_variant(self, variant_name: str) -> "Study":
"""
pass

@abstractmethod
def update_multiple_binding_constraints(
self, new_properties: Dict[str, "BindingConstraintPropertiesUpdate"]
) -> Dict[str, "BindingConstraintProperties"]:
pass

@abstractmethod
def move_study(self, new_parent_path: Path) -> PurePath:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class BindingConstraintPropertiesLocal(LocalBaseModel):
@staticmethod
def from_user_model(user_class: BindingConstraintPropertiesType) -> "BindingConstraintPropertiesLocal":
user_dict = asdict(user_class)
return BindingConstraintPropertiesLocal.model_validate(user_dict)
dict_without_none = {k: v for k, v in user_dict.items() if v is not None}
return BindingConstraintPropertiesLocal.model_validate(dict_without_none)

def to_user_model(self) -> BindingConstraintProperties:
return BindingConstraintProperties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
)
from antares.craft.model.binding_constraint import (
BindingConstraint,
BindingConstraintOperator,
BindingConstraintProperties,
BindingConstraintPropertiesUpdate,
ConstraintMatrixName,
Expand All @@ -30,6 +29,7 @@
)
from antares.craft.service.base_services import BaseBindingConstraintService
from antares.craft.service.local_services.models.binding_constraint import BindingConstraintPropertiesLocal
from antares.craft.service.local_services.services.utils import checks_matrix_dimensions
from antares.craft.tools.contents_tool import transform_name_to_id
from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes
from antares.craft.tools.matrix_tool import read_timeseries, write_timeseries
Expand Down Expand Up @@ -83,29 +83,15 @@ def _store_time_series(
equal_term_matrix: Optional[pd.DataFrame],
greater_term_matrix: Optional[pd.DataFrame],
) -> None:
# Lesser or greater can happen together when operator is both
if constraint.properties.operator in (BindingConstraintOperator.LESS, BindingConstraintOperator.BOTH):
write_timeseries(
self.config.study_path,
less_term_matrix,
TimeSeriesFileType.BINDING_CONSTRAINT_LESS,
constraint_id=constraint.id,
)
if constraint.properties.operator in (BindingConstraintOperator.GREATER, BindingConstraintOperator.BOTH):
write_timeseries(
self.config.study_path,
greater_term_matrix,
TimeSeriesFileType.BINDING_CONSTRAINT_GREATER,
constraint_id=constraint.id,
)
# Equal is always exclusive
if constraint.properties.operator == BindingConstraintOperator.EQUAL:
write_timeseries(
self.config.study_path,
equal_term_matrix,
TimeSeriesFileType.BINDING_CONSTRAINT_EQUAL,
constraint_id=constraint.id,
)
study_path = self.config.study_path
bc_id = constraint.id
write_timeseries(study_path, less_term_matrix, TimeSeriesFileType.BINDING_CONSTRAINT_LESS, constraint_id=bc_id)
write_timeseries(
study_path, greater_term_matrix, TimeSeriesFileType.BINDING_CONSTRAINT_GREATER, constraint_id=bc_id
)
write_timeseries(
study_path, equal_term_matrix, TimeSeriesFileType.BINDING_CONSTRAINT_EQUAL, constraint_id=bc_id
)

def _create_constraint_inside_ini(
self,
Expand Down Expand Up @@ -176,10 +162,14 @@ def update_binding_constraint_properties(
current_ini_content = self.ini_file.ini_dict
existing_constraint = self._get_constraint_inside_ini(current_ini_content, binding_constraint)
local_properties = BindingConstraintPropertiesLocal.from_user_model(properties)
existing_constraint.update(local_properties.model_dump(mode="json", by_alias=True))
existing_constraint.update(local_properties.model_dump(mode="json", by_alias=True, exclude_unset=True))
self.ini_file.ini_dict = current_ini_content
self.ini_file.write_ini_file()
return local_properties.to_user_model()
# Return a user object based on what's written inside the INI file
del existing_constraint["name"]
del existing_constraint["id"]
modified_local_properties = BindingConstraintPropertiesLocal.model_validate(existing_constraint)
return modified_local_properties.to_user_model()

@override
def get_constraint_matrix(self, constraint: BindingConstraint, matrix_name: ConstraintMatrixName) -> pd.DataFrame:
Expand All @@ -189,7 +179,15 @@ def get_constraint_matrix(self, constraint: BindingConstraint, matrix_name: Cons
def update_constraint_matrix(
self, constraint: BindingConstraint, matrix_name: ConstraintMatrixName, matrix: pd.DataFrame
) -> None:
raise NotImplementedError
checks_matrix_dimensions(
matrix, f"bindingconstraints/{constraint.id}", f"bc_{constraint.properties.time_step.value}"
)
write_timeseries(
self.config.study_path,
matrix,
MAPPING[matrix_name],
constraint_id=constraint.id,
)

@override
def read_binding_constraints(self) -> list[BindingConstraint]:
Expand Down Expand Up @@ -231,6 +229,17 @@ def read_binding_constraints(self) -> list[BindingConstraint]:
constraints.sort(key=lambda bc: bc.id)
return constraints

@override
def update_multiple_binding_constraints(
self, new_properties: dict[str, BindingConstraintPropertiesUpdate]
) -> dict[str, BindingConstraintProperties]:
new_properties_dict = {}
for constraint_id, new_props in new_properties.items():
bc = BindingConstraint(constraint_id, self)
modified_properties = self.update_binding_constraint_properties(bc, new_props)
new_properties_dict[constraint_id] = modified_properties
return new_properties_dict

@staticmethod
def _get_constraint_inside_ini(ini_content: dict[str, Any], constraint: BindingConstraint) -> dict[str, Any]:
existing_key = next((key for key, bc in ini_content.items() if bc["id"] == constraint.id), None)
Expand Down
8 changes: 0 additions & 8 deletions src/antares/craft/service/local_services/services/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
from antares.craft.config.local_configuration import LocalConfiguration
from antares.craft.model.binding_constraint import (
BindingConstraint,
BindingConstraintProperties,
BindingConstraintPropertiesUpdate,
)
from antares.craft.model.output import Output
from antares.craft.service.base_services import BaseOutputService, BaseStudyService
Expand Down Expand Up @@ -77,9 +75,3 @@ def move_study(self, new_parent_path: Path) -> PurePath:
@override
def generate_thermal_timeseries(self, number_of_years: int) -> None:
raise NotImplementedError

@override
def update_multiple_binding_constraints(
self, new_properties: dict[str, BindingConstraintPropertiesUpdate]
) -> dict[str, BindingConstraintProperties]:
raise NotImplementedError
3 changes: 3 additions & 0 deletions src/antares/craft/service/local_services/services/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def __format__(self, format_spec: str) -> str:
STStorageMatrixName.INFLOWS.value: (8760, 1),
"series": (8760, AlwaysEqual()),
"links_parameters": (8760, 6),
"bc_hourly": (8784, AlwaysEqual()),
"bc_daily": (366, AlwaysEqual()),
"bc_weekly": (366, AlwaysEqual()),
}


Expand Down
69 changes: 69 additions & 0 deletions tests/antares/services/local_services/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
import pytest

import re

import pandas as pd

from antares.craft import Study
from antares.craft.exceptions.exceptions import MatrixFormatError
from antares.craft.model.binding_constraint import (
BindingConstraintOperator,
BindingConstraintProperties,
BindingConstraintPropertiesUpdate,
ConstraintTerm,
LinkData,
)
Expand All @@ -33,3 +39,66 @@ def test_read_constraints(self, local_study_w_constraints: Study) -> None:
assert bc_2.name == "bc_2"
assert bc_2.properties == BindingConstraintProperties()
assert bc_2.get_terms() == {"at%fr": ConstraintTerm(data=LinkData(area1="at", area2="fr"), weight=2)}

def test_update_properties(self, local_study_w_constraints: Study) -> None:
# Checks values before update
bc = local_study_w_constraints.get_binding_constraints()["bc_1"]
current_properties = BindingConstraintProperties(operator=BindingConstraintOperator.GREATER, enabled=False)
assert bc.properties == current_properties
# Updates properties
update_properties = BindingConstraintPropertiesUpdate(
operator=BindingConstraintOperator.LESS, comments="new comment"
)
new_properties = bc.update_properties(update_properties)
expected_properties = BindingConstraintProperties(
enabled=False, operator=BindingConstraintOperator.LESS, comments="new comment"
)
assert new_properties == expected_properties
assert bc.properties == expected_properties

def test_update_multiple_update_properties(self, local_study_w_constraints: Study) -> None:
# Checks values before update
bc_1 = local_study_w_constraints.get_binding_constraints()["bc_1"]
bc_2 = local_study_w_constraints.get_binding_constraints()["bc_2"]

current_properties = BindingConstraintProperties(operator=BindingConstraintOperator.GREATER, enabled=False)
assert bc_1.properties == current_properties
assert bc_2.properties == BindingConstraintProperties()

# Updates properties
update_properties_1 = BindingConstraintPropertiesUpdate(group="group_1")
update_properties_2 = BindingConstraintPropertiesUpdate(enabled=False)
body = {bc_1.id: update_properties_1, bc_2.id: update_properties_2}
local_study_w_constraints.update_multiple_binding_constraints(body)
# Asserts constraints properties were modified
assert bc_1.properties == BindingConstraintProperties(
group="group_1", operator=BindingConstraintOperator.GREATER, enabled=False
)
assert bc_2.properties == BindingConstraintProperties(group="default", enabled=False)

def test_matrices(self, local_study_w_constraints: Study) -> None:
bc = local_study_w_constraints.get_binding_constraints()["bc_1"]

# Replace matrices
matrix = pd.DataFrame(data=8784 * [[3]])
assert bc.get_greater_term_matrix().empty
bc.update_greater_term_matrix(matrix)
assert bc.get_greater_term_matrix().equals(matrix)

assert bc.get_less_term_matrix().empty
bc.update_less_term_matrix(matrix)
assert bc.get_less_term_matrix().equals(matrix)

assert bc.get_equal_term_matrix().empty
bc.update_equal_term_matrix(matrix)
assert bc.get_equal_term_matrix().equals(matrix)

# Try to update with wrongly formatted matrix
matrix = pd.DataFrame(data=[[1, 2, 3], [4, 5, 6]])
with pytest.raises(
MatrixFormatError,
match=re.escape(
"Wrong format for bindingconstraints/bc_1/bc_hourly matrix, expected shape is (8784, Any) and was : (2, 3)"
),
):
bc.update_less_term_matrix(matrix)

0 comments on commit 92ca409

Please sign in to comment.