Skip to content

Commit

Permalink
fix(xpansion): fix several issues related to weights and constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBelthle committed Dec 18, 2024
1 parent 4e70a35 commit b5ce412
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 24 deletions.
44 changes: 24 additions & 20 deletions antarest/study/business/xpansion_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def from_config(cls, config_obj: JSON) -> "GetXpansionSettings":
try:
return cls(**config_obj)
except ValidationError:
return cls.construct(**config_obj)
return cls.model_construct(**config_obj)


@all_optional_model
Expand Down Expand Up @@ -270,11 +270,6 @@ def __init__(self) -> None:
super().__init__(http.HTTPStatus.BAD_REQUEST)


class WrongTypeFormat(HTTPException):
def __init__(self, message: str) -> None:
super().__init__(http.HTTPStatus.BAD_REQUEST, message)


class WrongLinkFormatError(HTTPException):
def __init__(self, message: str) -> None:
super().__init__(http.HTTPStatus.BAD_REQUEST, message)
Expand All @@ -295,11 +290,6 @@ def __init__(self, message: str) -> None:
super().__init__(http.HTTPStatus.NOT_FOUND, message)


class ConstraintsNotFoundError(HTTPException):
def __init__(self, message: str) -> None:
super().__init__(http.HTTPStatus.NOT_FOUND, message)


class FileCurrentlyUsedInSettings(HTTPException):
def __init__(self, message: str) -> None:
super().__init__(http.HTTPStatus.CONFLICT, message)
Expand Down Expand Up @@ -337,7 +327,10 @@ def create_xpansion_configuration(self, study: Study, zipped_config: t.Optional[

xpansion_settings = XpansionSettings()
settings_obj = xpansion_settings.model_dump(
mode="json", by_alias=True, exclude_none=True, exclude={"sensitivity_config"}
mode="json",
by_alias=True,
exclude_none=True,
exclude={"sensitivity_config", "yearly_weights", "additional_constraints"},
)
if xpansion_settings.sensitivity_config:
sensitivity_obj = xpansion_settings.sensitivity_config.model_dump(
Expand Down Expand Up @@ -389,22 +382,33 @@ def update_xpansion_settings(

file_study = self.study_storage_service.get_storage(study).get_raw(study)

# Specific handling of the additional constraints file:
# - If the file name is `None`, it means that the user does not want to select an additional constraints file.
# - If the file name is empty, it means that the user wants to deselect the additional constraints file,
# but he does not want to delete it from the expansion configuration folder.
# - If the file name is not empty, it means that the user wants to select an additional constraints file.
# Specific handling for yearly_weights and additional_constraints:
# - If the attributes are given, it means that the user wants to select a file.
# It is therefore necessary to check that the file exists.
constraints_file = new_xpansion_settings.additional_constraints
if constraints_file:
# - Else, it means the user want to deselect the additional constraints file,
# but he does not want to delete it from the expansion configuration folder.
excludes = {"sensitivity_config"}
if constraints_file := new_xpansion_settings.additional_constraints:
try:
constraints_url = ["user", "expansion", "constraints", constraints_file]
file_study.tree.get(constraints_url)
except ChildNotFoundError:
msg = f"Additional constraints file '{constraints_file}' does not exist"
raise XpansionFileNotFoundError(msg) from None
else:
excludes.add("additional_constraints")

if weights_file := new_xpansion_settings.yearly_weights:
try:
weights_url = ["user", "expansion", "weights", weights_file]
file_study.tree.get(weights_url)
except ChildNotFoundError:
msg = f"Additional weights file '{weights_file}' does not exist"
raise XpansionFileNotFoundError(msg) from None
else:
excludes.add("yearly_weights")

config_obj = updated_settings.model_dump(mode="json", by_alias=True, exclude={"sensitivity_config"})
config_obj = updated_settings.model_dump(mode="json", by_alias=True, exclude=excludes)
file_study.tree.save(config_obj, ["user", "expansion", "settings"])

if new_xpansion_settings.sensitivity_config:
Expand Down
61 changes: 57 additions & 4 deletions tests/storage/business/test_xpansion_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ def set_up_xpansion_manager(tmp_path: Path) -> t.Tuple[FileStudy, RawStudy, Xpan
"log_level": 0,
"separation_parameter": 0.5,
"batch_size": 96,
"yearly-weights": "",
"additional-constraints": "",
"timelimit": int(1e12),
},
"weights": {},
Expand Down Expand Up @@ -228,8 +226,6 @@ def test_update_xpansion_settings(tmp_path: Path) -> None:
"max_iteration": 123,
"uc_type": UcType.EXPANSION_FAST,
"master": Master.INTEGER,
"yearly-weights": "",
"additional-constraints": "",
"relaxed_optimality_gap": "1.2%", # percentage
"relative_gap": 1e-12,
"batch_size": 4,
Expand Down Expand Up @@ -463,6 +459,63 @@ def test_update_constraints(tmp_path: Path) -> None:
assert actual_settings.additional_constraints == ""


@pytest.mark.unit_test
def test_update_constraints_via_the_front(tmp_path: Path) -> None:
empty_study, study, xpansion_manager = set_up_xpansion_manager(tmp_path)
empty_study.tree.save({"user": {"expansion": {"constraints": {"constraints.txt": b"0"}}}})

# asserts we can update a field without writing the field additional constraint in the file
front_settings = UpdateXpansionSettings(master="relaxed")
xpansion_manager.update_xpansion_settings(study, front_settings)
json_content = empty_study.tree.get(["user", "expansion", "settings"])
assert "additional-constraints" not in json_content
assert json_content["master"] == "relaxed"

# asserts the front-end can fill additional constraints
new_constraint = {"additional-constraints": "constraints.txt"}
front_settings = UpdateXpansionSettings.model_validate(new_constraint)
actual_settings = xpansion_manager.update_xpansion_settings(study, front_settings)
assert actual_settings.additional_constraints == "constraints.txt"
json_content = empty_study.tree.get(["user", "expansion", "settings"])
assert json_content["additional-constraints"] == "constraints.txt"

# asserts the front-end can unselect this constraint by not filling it
front_settings = UpdateXpansionSettings()
actual_settings = xpansion_manager.update_xpansion_settings(study, front_settings)
assert actual_settings.additional_constraints == ""
json_content = empty_study.tree.get(["user", "expansion", "settings"])
assert "additional-constraints" not in json_content


@pytest.mark.unit_test
def test_update_weights_via_the_front(tmp_path: Path) -> None:
# Same test as the one for constraints
empty_study, study, xpansion_manager = set_up_xpansion_manager(tmp_path)
empty_study.tree.save({"user": {"expansion": {"weights": {"weights.txt": b"0"}}}})

# asserts we can update a field without writing the field yearly-weights in the file
front_settings = UpdateXpansionSettings(master="relaxed")
xpansion_manager.update_xpansion_settings(study, front_settings)
json_content = empty_study.tree.get(["user", "expansion", "settings"])
assert "yearly-weights" not in json_content
assert json_content["master"] == "relaxed"

# asserts the front-end can fill yearly weights
new_constraint = {"yearly-weights": "weights.txt"}
front_settings = UpdateXpansionSettings.model_validate(new_constraint)
actual_settings = xpansion_manager.update_xpansion_settings(study, front_settings)
assert actual_settings.yearly_weights == "weights.txt"
json_content = empty_study.tree.get(["user", "expansion", "settings"])
assert json_content["yearly-weights"] == "weights.txt"

# asserts the front-end can unselect this weight by not filling it
front_settings = UpdateXpansionSettings()
actual_settings = xpansion_manager.update_xpansion_settings(study, front_settings)
assert actual_settings.yearly_weights == ""
json_content = empty_study.tree.get(["user", "expansion", "settings"])
assert "yearly-weights" not in json_content


@pytest.mark.unit_test
def test_add_resources(tmp_path: Path) -> None:
empty_study, study, xpansion_manager = set_up_xpansion_manager(tmp_path)
Expand Down

0 comments on commit b5ce412

Please sign in to comment.