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

feat(binding-constraints): create UpdateConstraint command #2274

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions antarest/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,11 @@ def __init__(self, message: str) -> None:
super().__init__(HTTPStatus.NOT_FOUND, message)


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


class NoConstraintError(HTTPException):
def __init__(self, message: str) -> None:
super().__init__(HTTPStatus.NOT_FOUND, message)
Expand Down
147 changes: 75 additions & 72 deletions antarest/study/business/binding_constraint_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@
from antarest.core.serialization import AntaresBaseModel
from antarest.core.utils.string import to_camel_case
from antarest.study.business.all_optional_meta import camel_case_model
from antarest.study.business.utils import execute_or_add_commands
from antarest.study.business.utils import execute_or_add_commands, update_binding_constraint_from_props
from antarest.study.model import STUDY_VERSION_8_3, STUDY_VERSION_8_7, Study
from antarest.study.storage.rawstudy.model.filesystem.config.binding_constraint import (
DEFAULT_GROUP,
DEFAULT_OPERATOR,
DEFAULT_TIMESTEP,
OPERATOR_MATRIX_FILE_MAP,
BindingConstraintFrequency,
BindingConstraintOperator,
)
Expand Down Expand Up @@ -68,18 +69,13 @@
TermMatrices,
create_binding_constraint_config,
)
from antarest.study.storage.variantstudy.model.command.icommand import ICommand
from antarest.study.storage.variantstudy.model.command.remove_binding_constraint import RemoveBindingConstraint
from antarest.study.storage.variantstudy.model.command.remove_multiple_binding_constraints import (
RemoveMultipleBindingConstraints,
)
from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix
from antarest.study.storage.variantstudy.model.command.update_binding_constraint import (
UpdateBindingConstraint,
update_matrices_names,
)
from antarest.study.storage.variantstudy.model.command.update_binding_constraint import UpdateBindingConstraint
from antarest.study.storage.variantstudy.model.command.update_binding_constraints import UpdateBindingConstraints
from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig
from antarest.study.storage.variantstudy.model.command_context import CommandContext
from antarest.study.storage.variantstudy.model.dbmodel import VariantStudy

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -350,13 +346,6 @@ class ConstraintOutput870(ConstraintOutput830):
# the type of the output constraint in the FastAPI endpoint.
ConstraintOutput = t.Union[ConstraintOutputBase, ConstraintOutput830, ConstraintOutput870]

OPERATOR_MATRIX_FILE_MAP = {
BindingConstraintOperator.EQUAL: ["{bc_id}_eq"],
BindingConstraintOperator.GREATER: ["{bc_id}_gt"],
BindingConstraintOperator.LESS: ["{bc_id}_lt"],
BindingConstraintOperator.BOTH: ["{bc_id}_lt", "{bc_id}_gt"],
}


def _get_references_by_widths(
file_study: FileStudy, bcs: t.Sequence[ConstraintOutput]
Expand Down Expand Up @@ -399,46 +388,6 @@ def _get_references_by_widths(
return references_by_width


def _generate_replace_matrix_commands(
bc_id: str,
study_version: StudyVersion,
value: ConstraintInput,
operator: BindingConstraintOperator,
command_context: CommandContext,
) -> t.List[ICommand]:
commands: t.List[ICommand] = []
if study_version < STUDY_VERSION_8_7:
matrix = {
BindingConstraintFrequency.HOURLY.value: default_bc_hourly_86,
BindingConstraintFrequency.DAILY.value: default_bc_weekly_daily_86,
BindingConstraintFrequency.WEEKLY.value: default_bc_weekly_daily_86,
}[value.time_step].tolist()
command = ReplaceMatrix(
target=f"input/bindingconstraints/{bc_id}",
matrix=matrix,
command_context=command_context,
study_version=study_version,
)
commands.append(command)
else:
matrix = {
BindingConstraintFrequency.HOURLY.value: default_bc_hourly_87,
BindingConstraintFrequency.DAILY.value: default_bc_weekly_daily_87,
BindingConstraintFrequency.WEEKLY.value: default_bc_weekly_daily_87,
}[value.time_step].tolist()
matrices_to_replace = OPERATOR_MATRIX_FILE_MAP[operator]
for matrix_name in matrices_to_replace:
matrix_id = matrix_name.format(bc_id=bc_id)
command = ReplaceMatrix(
target=f"input/bindingconstraints/{matrix_id}",
matrix=matrix,
command_context=command_context,
study_version=study_version,
)
commands.append(command)
return commands


def _validate_binding_constraints(file_study: FileStudy, bcs: t.Sequence[ConstraintOutput]) -> bool:
"""
Validates the binding constraints within a group.
Expand Down Expand Up @@ -982,26 +931,15 @@ def update_binding_constraints(
if bc_id not in dict_config:
raise BindingConstraintNotFound(f"Binding constraint '{bc_id}' not found")

props = create_binding_constraint_config(study_version, **value.dict())
new_values = props.model_dump(mode="json", by_alias=True, exclude_unset=True)
# convert table mode object to an object that's the output of this function
# and we also update the cofig objet that will be serialized in the INI
bc_props = create_binding_constraint_config(study_version, **value.dict())
upd_obj = config[dict_config[bc_id]]
current_value = copy.deepcopy(upd_obj)
upd_obj.update(new_values)
output = self.constraint_model_adapter(upd_obj, study_version)
upd_obj_copy = copy.deepcopy(upd_obj)
update_binding_constraint_from_props(upd_obj, bc_props)
output = self.constraint_model_adapter(upd_obj_copy, study_version)
updated_constraints[bc_id] = output

if value.time_step and value.time_step != BindingConstraintFrequency(current_value["type"]):
# The user changed the time step, we need to update the matrix accordingly
replace_matrix_commands = _generate_replace_matrix_commands(
bc_id, study_version, value, output.operator, command_context
)
commands.extend(replace_matrix_commands)

if value.operator and study_version >= STUDY_VERSION_8_7:
# The user changed the operator, we have to rename matrices accordingly
existing_operator = BindingConstraintOperator(current_value["operator"])
update_matrices_names(file_study, bc_id, existing_operator, value.operator)

# Updates the file only once with all the information
command = UpdateConfig(
target="input/bindingconstraints/bindingconstraints",
Expand All @@ -1013,6 +951,71 @@ def update_binding_constraints(
execute_or_add_commands(study, file_study, commands, self.storage_service)
return updated_constraints

def update_binding_constraints_2(
self,
study: Study,
bcs_by_ids: t.Mapping[str, ConstraintInput],
) -> t.Mapping[str, ConstraintOutput]:
"""
WIP
"""

# Variant study with less than 50 updated constraints
updated_constraints = {}
if len(bcs_by_ids) < 50 and isinstance(study, VariantStudy):
existing_constraints = {bc.id: bc for bc in self.get_binding_constraints(study)}
for bc_id, data in bcs_by_ids.items():
updated_constraints[bc_id] = self.update_binding_constraint(
study, bc_id, data, existing_constraints[bc_id]
)
return updated_constraints

# More efficient way of doing things but using less readable commands.
study_version = StudyVersion.parse(study.version)
command_context = self.storage_service.variant_study_service.command_factory.command_context

file_study = self.storage_service.get_storage(study).get_raw(study)
bcs_config = file_study.tree.get(["input", "bindingconstraints", "bindingconstraints"])
bcs_config_by_id = {value["id"]: key for (key, value) in bcs_config.items()}
# bc_props_by_id = {}
bc_input_as_dict_by_id = {}
for bc_id, bc_input in bcs_by_ids.items():
if bc_id not in bcs_config_by_id:
raise BindingConstraintNotFound(f"Binding constraint '{bc_id}' not found")

# convert table mode object to an object that's the output of this function
# and we also update the cofig objet that will be serialized in the INI
bc_input_as_dict = bc_input.model_dump(mode="json", exclude_unset=True)
input_bc_props = create_binding_constraint_config(study_version, **bc_input_as_dict)
input_bc_props_as_dict = input_bc_props.model_dump(mode="json", by_alias=True, exclude_unset=True)
bc_config = bcs_config[bcs_config_by_id[bc_id]]
bc_config_copy = copy.deepcopy(bc_config)
bc_config_copy.update(input_bc_props_as_dict)
bc_output = self.constraint_model_adapter(bc_config_copy, study_version)
updated_constraints[bc_id] = bc_output
bc_input_as_dict_by_id[bc_id] = bc_input_as_dict

# if value.time_step and value.time_step != BindingConstraintFrequency(bc_config_copy["type"]):
# # The user changed the time step, we need to update the matrix accordingly
# replace_matrix_commands = _generate_replace_matrix_commands(
# bc_id, study_version, value, bc_output.operator, command_context
# )
# commands.extend(replace_matrix_commands)

# if value.operator and study_version >= STUDY_VERSION_8_7:
# # The user changed the operator, we have to rename matrices accordingly
# existing_operator = BindingConstraintOperator(bc_config_copy["operator"])
# update_matrices_names(file_study, bc_id, existing_operator, value.operator)

# Updates the file only once with all the information
command = UpdateBindingConstraints(
bc_props_by_id=bc_input_as_dict_by_id,
command_context=command_context,
study_version=study_version,
)
execute_or_add_commands(study, file_study, [command], self.storage_service)
return updated_constraints

def remove_binding_constraint(self, study: Study, binding_constraint_id: str) -> None:
"""
Removes a binding constraint from a study.
Expand Down
2 changes: 1 addition & 1 deletion antarest/study/business/table_mode_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def update_table_data(
return data
elif table_type == TableModeType.BINDING_CONSTRAINT:
bcs_by_ids = {key: ConstraintInput(**values) for key, values in data.items()}
bcs_map = self._binding_constraint_manager.update_binding_constraints(study, bcs_by_ids)
bcs_map = self._binding_constraint_manager.update_binding_constraints_2(study, bcs_by_ids)
return {
bc_id: bc.model_dump(by_alias=True, exclude={"id", "name", "terms"}) for bc_id, bc in bcs_map.items()
}
Expand Down
6 changes: 6 additions & 0 deletions antarest/study/business/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from antarest.study.storage.storage_service import StudyStorageService
from antarest.study.storage.utils import is_managed
from antarest.study.storage.variantstudy.business.utils import transform_command_to_dto
from antarest.study.storage.variantstudy.model.command.create_binding_constraint import BindingConstraintProperties
from antarest.study.storage.variantstudy.model.command.icommand import ICommand
from antarest.study.storage.variantstudy.model.command_listener.command_listener import ICommandListener

Expand Down Expand Up @@ -98,3 +99,8 @@ class FieldInfo(t.TypedDict, total=False):
encode: t.Optional[t.Callable[[t.Any], t.Any]]
# (encoded_value, current_value) -> decoded_value
decode: t.Optional[t.Callable[[t.Any, t.Optional[t.Any]], t.Any]]


def update_binding_constraint_from_props(bc: t.Dict, bc_props: t.Type[BindingConstraintProperties]):
bc_props_as_dict = bc_props.model_dump(mode="json", by_alias=True, exclude_unset=True)
bc.update(bc_props_as_dict)
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class BindingConstraintOperator(EnumIgnoreCase):
BindingConstraintOperator.BOTH: ["lt", "gt"],
}

OPERATOR_MATRIX_FILE_MAP = {
BindingConstraintOperator.EQUAL: ["{bc_id}_eq"],
BindingConstraintOperator.GREATER: ["{bc_id}_gt"],
BindingConstraintOperator.LESS: ["{bc_id}_lt"],
BindingConstraintOperator.BOTH: ["{bc_id}_lt", "{bc_id}_gt"],
}

DEFAULT_GROUP = "default"
"""Default group for binding constraints (since v8.7)."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from antarest.study.storage.variantstudy.model.command.remove_user_resource import RemoveUserResource
from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix
from antarest.study.storage.variantstudy.model.command.update_binding_constraint import UpdateBindingConstraint
from antarest.study.storage.variantstudy.model.command.update_binding_constraints import UpdateBindingConstraints
from antarest.study.storage.variantstudy.model.command.update_comments import UpdateComments
from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig
from antarest.study.storage.variantstudy.model.command.update_district import UpdateDistrict
Expand Down Expand Up @@ -158,6 +159,14 @@ def _revert_update_binding_constraint(

return base_command.get_command_extractor().extract_binding_constraint(base, base_command.id)

@staticmethod
def _revert_update_binding_constraints(
base_command: UpdateBindingConstraints,
history: t.List["ICommand"],
base: FileStudy,
) -> t.List[ICommand]:
raise NotImplementedError("The revert function for UpdateBindingConstraints is not available")

@staticmethod
def _revert_remove_binding_constraint(
base_command: RemoveBindingConstraint,
Expand Down
2 changes: 2 additions & 0 deletions antarest/study/storage/variantstudy/command_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from antarest.study.storage.variantstudy.model.command.remove_user_resource import RemoveUserResource
from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix
from antarest.study.storage.variantstudy.model.command.update_binding_constraint import UpdateBindingConstraint
from antarest.study.storage.variantstudy.model.command.update_binding_constraints import UpdateBindingConstraints
from antarest.study.storage.variantstudy.model.command.update_comments import UpdateComments
from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig
from antarest.study.storage.variantstudy.model.command.update_district import UpdateDistrict
Expand All @@ -65,6 +66,7 @@
CommandName.REMOVE_LINK.value: RemoveLink,
CommandName.CREATE_BINDING_CONSTRAINT.value: CreateBindingConstraint,
CommandName.UPDATE_BINDING_CONSTRAINT.value: UpdateBindingConstraint,
CommandName.UPDATE_BINDING_CONSTRAINTS.value: UpdateBindingConstraints,
CommandName.REMOVE_BINDING_CONSTRAINT.value: RemoveBindingConstraint,
CommandName.REMOVE_MULTIPLE_BINDING_CONSTRAINTS.value: RemoveMultipleBindingConstraints,
CommandName.CREATE_THERMAL_CLUSTER.value: CreateCluster,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CommandName(Enum):
CREATE_BINDING_CONSTRAINT = "create_binding_constraint"
UPDATE_BINDING_CONSTRAINT = "update_binding_constraint"
REMOVE_BINDING_CONSTRAINT = "remove_binding_constraint"
UPDATE_BINDING_CONSTRAINTS = "update_binding_constraints"
REMOVE_MULTIPLE_BINDING_CONSTRAINTS = "remove_multiple_binding_constraints"
CREATE_THERMAL_CLUSTER = "create_cluster"
REMOVE_THERMAL_CLUSTER = "remove_cluster"
Expand Down
Loading
Loading