Skip to content

Commit

Permalink
BindingConstraint service refactored (#25)
Browse files Browse the repository at this point in the history
* conflits resolved

* binding_constraints terms properly written

* lint applied

* silly comments removed

* area_name_errors message restore

* serialized_terms_data method moved to ConstraintTerm as class method

* weight_offset used for ConstraintTerms serialization

* serialize_term_data removed
  • Loading branch information
vargastat authored Dec 5, 2024
1 parent 795d14d commit b5bf3a2
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 26 deletions.
23 changes: 20 additions & 3 deletions src/antares/model/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ def __init__(
self._settings = DefaultStudySettings.model_validate(settings if settings is not None else StudySettings())
self._areas: Dict[str, Area] = dict()
self._links: Dict[str, Link] = dict()
self._binding_constraints: Dict[str, BindingConstraint] = dict()

@property
def service(self) -> BaseStudyService:
Expand All @@ -229,7 +230,7 @@ def get_settings(self) -> DefaultStudySettings:
return self._settings

def get_binding_constraints(self) -> MappingProxyType[str, BindingConstraint]:
return MappingProxyType(self._binding_constraints_service.binding_constraints)
return MappingProxyType(self._binding_constraints)

def create_area(
self, area_name: str, *, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None
Expand Down Expand Up @@ -269,9 +270,25 @@ def create_binding_constraint(
equal_term_matrix: Optional[pd.DataFrame] = None,
greater_term_matrix: Optional[pd.DataFrame] = None,
) -> BindingConstraint:
return self._binding_constraints_service.create_binding_constraint(
"""
Create a new binding constraint and store it.
Args:
name (str): The name of the binding constraint.
properties (Optional[BindingConstraintProperties]): Optional properties for the constraint.
terms (Optional[List[ConstraintTerm]]): Optional list of terms for the constraint.
less_term_matrix (Optional[pd.DataFrame]): Optional less-than term matrix.
equal_term_matrix (Optional[pd.DataFrame]): Optional equality term matrix.
greater_term_matrix (Optional[pd.DataFrame]): Optional greater-than term matrix.
Returns:
BindingConstraint: The created binding constraint.
"""
binding_constraint = self._binding_constraints_service.create_binding_constraint(
name, properties, terms, less_term_matrix, equal_term_matrix, greater_term_matrix
)
self._binding_constraints[binding_constraint.id] = binding_constraint
return binding_constraint

def update_settings(self, settings: StudySettings) -> None:
new_settings = self._study_service.update_study_settings(settings)
Expand All @@ -280,7 +297,7 @@ def update_settings(self, settings: StudySettings) -> None:

def delete_binding_constraint(self, constraint: BindingConstraint) -> None:
self._study_service.delete_binding_constraint(constraint)
self._binding_constraints_service.binding_constraints.pop(constraint.id)
self._binding_constraints.pop(constraint.id)

def delete(self, children: bool = False) -> None:
self._study_service.delete(children)
Expand Down
2 changes: 0 additions & 2 deletions src/antares/service/api_services/binding_constraint_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def __init__(self, config: APIconf, study_id: str) -> None:
self.study_id = study_id
self._wrapper = RequestWrapper(self.api_config.set_up_api_conf())
self._base_url = f"{self.api_config.get_host()}/api/v1"
self.binding_constraints = {}

def create_binding_constraint(
self,
Expand Down Expand Up @@ -105,7 +104,6 @@ def create_binding_constraint(
raise BindingConstraintCreationError(name, e.message) from e

constraint = BindingConstraint(name, self, bc_properties, bc_terms)
self.binding_constraints[constraint.id] = constraint

return constraint

Expand Down
101 changes: 81 additions & 20 deletions src/antares/service/local_services/binding_constraint_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.

from typing import Any, Optional

import numpy as np
Expand All @@ -22,6 +21,7 @@
BindingConstraintFrequency,
BindingConstraintOperator,
BindingConstraintProperties,
BindingConstraintPropertiesLocal,
ConstraintMatrixName,
ConstraintTerm,
)
Expand All @@ -37,7 +37,6 @@ def __init__(self, config: LocalConfiguration, study_name: str, **kwargs: Any) -
self.config = config
self.study_name = study_name
self.ini_file = IniFile(self.config.study_path, IniFileTypes.BINDING_CONSTRAINTS_INI)
self.binding_constraints = {}

def create_binding_constraint(
self,
Expand All @@ -54,17 +53,16 @@ def create_binding_constraint(
properties=properties,
terms=terms,
)
if constraint.id in self.binding_constraints:
constraint.properties = constraint.local_properties.yield_binding_constraint_properties()

current_ini_content = self.ini_file.ini_dict_binding_constraints or {}
if any(values.get("id") == constraint.id for values in current_ini_content.values()):
raise BindingConstraintCreationError(
constraint_name=name, message=f"A binding constraint with the name '{name}' already exists."
constraint_name=name, message=f"A binding constraint with the name {name} already exists."
)
constraint.properties = constraint.local_properties.yield_binding_constraint_properties()

# Add binding constraints
self.binding_constraints[constraint.id] = constraint
self._write_binding_constraint_ini()
self._write_binding_constraint_ini(constraint.properties, name, name, terms)

# Add constraint time series
self._store_time_series(constraint, less_term_matrix, equal_term_matrix, greater_term_matrix)

return constraint
Expand Down Expand Up @@ -103,21 +101,84 @@ def _check_if_empty_ts(time_step: BindingConstraintFrequency, time_series: Optio
time_series_length = (365 * 24 + 24) if time_step == BindingConstraintFrequency.HOURLY else 366
return time_series if time_series is not None else pd.DataFrame(np.zeros([time_series_length, 1]))

def _write_binding_constraint_ini(self) -> None:
binding_constraints_ini_content = {
idx: idx_constraint.local_properties.list_ini_fields
for idx, idx_constraint in enumerate(self.binding_constraints.values())
}
self.ini_file.ini_dict = binding_constraints_ini_content
def _write_binding_constraint_ini(
self,
properties: BindingConstraintProperties,
constraint_name: str,
constraint_id: str,
terms: Optional[list[ConstraintTerm]] = None,
) -> None:
"""
Write or update a binding constraint in the INI file.
"""

current_ini_content = self.ini_file.ini_dict_binding_constraints or {}

existing_section = next(
(section for section, values in current_ini_content.items() if values.get("name") == constraint_name),
None,
)

if existing_section:
existing_terms = current_ini_content[existing_section]

serialized_terms = {term.id: term.weight_offset() for term in terms} if terms else {}

existing_terms.update(serialized_terms) # type: ignore
current_ini_content[existing_section] = existing_terms

# Persist the updated INI content
self.ini_file.write_ini_file()
else:
terms_dict = {term.id: term for term in terms} if terms else {}

full_properties = BindingConstraintPropertiesLocal(
constraint_name=constraint_name,
constraint_id=constraint_id,
terms=terms_dict,
**properties.model_dump(),
)

section_index = len(current_ini_content)
current_ini_content[str(section_index)] = full_properties.list_ini_fields

self.ini_file.ini_dict_binding_constraints = current_ini_content
self.ini_file.write_ini_file()

def add_constraint_terms(self, constraint: BindingConstraint, terms: list[ConstraintTerm]) -> list[ConstraintTerm]:
new_terms = constraint.local_properties.terms | {
term.id: term for term in terms if term.id not in constraint.get_terms()
}
"""
Add terms to a binding constraint and update the INI file.
Args:
constraint (BindingConstraint): The binding constraint to update.
terms (list[ConstraintTerm]): A list of new terms to add.
Returns:
list[ConstraintTerm]: The updated list of terms.
"""

new_terms = constraint.local_properties.terms.copy()

for term in terms:
if term.id in constraint.get_terms():
raise BindingConstraintCreationError(
constraint_name=constraint.name, message=f"Duplicate term found: {term.id}"
)
new_terms[term.id] = term

constraint.local_properties.terms = new_terms
self._write_binding_constraint_ini()
return list(new_terms.values())

terms_values = list(new_terms.values())

self._write_binding_constraint_ini(
properties=constraint.properties,
constraint_name=constraint.name,
constraint_id=constraint.id,
terms=terms_values,
)

return terms_values

def delete_binding_constraint_term(self, constraint_id: str, term_id: str) -> None:
raise NotImplementedError
Expand Down
13 changes: 13 additions & 0 deletions src/antares/tools/ini_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ def ini_dict(self, new_ini_dict: dict[str, dict[str, str]]) -> None:
self._ini_contents = CustomRawConfigParser()
self._ini_contents.read_dict(new_ini_dict)

@property
def ini_dict_binding_constraints(self) -> dict[str, dict[str, str]]:
return {section: dict(self._ini_contents[section]) for section in self._ini_contents.sections()}

@ini_dict_binding_constraints.setter
def ini_dict_binding_constraints(self, new_ini_dict: dict[str, dict[str, str]]) -> None:
"""Set INI file contents for binding constraints."""
self._ini_contents = CustomRawConfigParser()
for index, values in enumerate(new_ini_dict.values()):
self._ini_contents.add_section(str(index))
for key, value in values.items():
self._ini_contents.set(str(index), key, value)

@property
def parsed_ini(self) -> CustomRawConfigParser:
"""Ini contents as a CustomRawConfigParser"""
Expand Down
2 changes: 1 addition & 1 deletion tests/antares/services/local_services/test_study.py
Original file line number Diff line number Diff line change
Expand Up @@ -2071,7 +2071,7 @@ def test_duplicate_name_errors(self, local_study_with_constraint):
# Then
with pytest.raises(
BindingConstraintCreationError,
match=f"Could not create the binding constraint {binding_constraint_name}: A binding constraint with the name '{binding_constraint_name}' already exists.",
match=f"Could not create the binding constraint {binding_constraint_name}: A binding constraint with the name {binding_constraint_name} already exists.",
):
local_study_with_constraint.create_binding_constraint(name=binding_constraint_name)

Expand Down

0 comments on commit b5bf3a2

Please sign in to comment.