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

fix(xpansion): fix several issues related to weights and constraints #2273

Merged
merged 1 commit into from
Dec 19, 2024
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
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
Loading