Skip to content

Commit

Permalink
feat(solver): add back-end support for v8.7 studies
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBelthle committed Mar 11, 2024
1 parent aeaf738 commit 0b27f16
Show file tree
Hide file tree
Showing 45 changed files with 1,828 additions and 743 deletions.
10 changes: 10 additions & 0 deletions antarest/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ def __init__(self, message: str) -> None:
super().__init__(HTTPStatus.BAD_REQUEST, message)


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


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


class MissingDataError(HTTPException):
def __init__(self, message: str) -> None:
super().__init__(HTTPStatus.NOT_FOUND, message)
Expand Down
8 changes: 4 additions & 4 deletions antarest/study/business/areas/thermal_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from antarest.study.model import Study
from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id
from antarest.study.storage.rawstudy.model.filesystem.config.thermal import (
Thermal860Config,
Thermal860Properties,
Thermal870Config,
Thermal870Properties,
ThermalConfigType,
create_thermal_config,
)
Expand All @@ -32,7 +32,7 @@


@camel_case_model
class ThermalClusterInput(Thermal860Properties, metaclass=AllOptionalMetaclass, use_none=True):
class ThermalClusterInput(Thermal870Properties, metaclass=AllOptionalMetaclass, use_none=True):
"""
Model representing the data structure required to edit an existing thermal cluster within a study.
"""
Expand Down Expand Up @@ -72,7 +72,7 @@ def to_config(self, study_version: t.Union[str, int]) -> ThermalConfigType:


@camel_case_model
class ThermalClusterOutput(Thermal860Config, metaclass=AllOptionalMetaclass, use_none=True):
class ThermalClusterOutput(Thermal870Config, metaclass=AllOptionalMetaclass, use_none=True):
"""
Model representing the output data structure to display the details of a thermal cluster within a study.
"""
Expand Down
432 changes: 318 additions & 114 deletions antarest/study/business/binding_constraint_management.py

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion antarest/study/business/table_mode_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class BindingConstraintColumns(FormFieldsBaseModel):
type: Optional[BindingConstraintFrequency]
operator: Optional[BindingConstraintOperator]
enabled: Optional[StrictBool]
group: Optional[StrictStr]


class ColumnInfo(TypedDict):
Expand Down Expand Up @@ -342,6 +343,10 @@ class PathVars(TypedDict, total=False):
"path": f"{BINDING_CONSTRAINT_PATH}/enabled",
"default_value": True,
},
"group": {
"path": f"{BINDING_CONSTRAINT_PATH}/group",
"default_value": None,
},
},
}

Expand Down Expand Up @@ -477,7 +482,9 @@ def set_table_data(

if current_binding:
col_values = columns.dict(exclude_none=True)
current_binding_dto = BindingConstraintManager.process_constraint(current_binding)
current_binding_dto = BindingConstraintManager.process_constraint(
current_binding, int(study.version)
)

commands.append(
UpdateBindingConstraint(
Expand Down
3 changes: 2 additions & 1 deletion antarest/study/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@
"840": "empty_study_840.zip",
"850": "empty_study_850.zip",
"860": "empty_study_860.zip",
"870": "empty_study_870.zip",
}

NEW_DEFAULT_STUDY_VERSION: str = "860"
NEW_DEFAULT_STUDY_VERSION: str = "870"


class StudyGroup(Base): # type:ignore
Expand Down
1 change: 0 additions & 1 deletion antarest/study/storage/rawstudy/ini_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def _write_line( # type:ignore
self, section_name, key, value
)
if value is not None or not self._allow_no_value: # type:ignore
# value = IniConfigParser.format_value(value)
value = delimiter + str(value).replace("\n", "\n\t")
else:
value = ""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import glob
import os
from pathlib import Path
from typing import Any, Callable, Dict, Optional

from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig
Expand Down Expand Up @@ -91,6 +94,23 @@ def build(self) -> TREE:
return children


class BindingConstraintMatrixList(FolderNode):
def __init__(
self,
context: ContextServer,
config: FileStudyTreeConfig,
matrix_class: Callable[[ContextServer, FileStudyTreeConfig], INode[Any, Any, Any]],
):
super().__init__(context, config)
self.matrix_class = matrix_class

def build(self) -> TREE:
return {
Path(file).stem: self.matrix_class(self.context, self.config.next_file(Path(file).name))
for file in glob.glob(os.path.join(self.config.path, "*.txt"))
}


class ThermalMatrixList(FolderNode):
def __init__(
self,
Expand Down
56 changes: 52 additions & 4 deletions antarest/study/storage/rawstudy/model/filesystem/config/thermal.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"LawOption",
"ThermalClusterGroup",
"ThermalProperties",
"Thermal860Properties",
"ThermalConfig",
"Thermal860Config",
"Thermal870Config",
"ThermalConfigType",
"create_thermal_config",
)
Expand Down Expand Up @@ -87,6 +87,16 @@ def _missing_(cls, value: object) -> t.Optional["ThermalClusterGroup"]:
return t.cast(t.Optional["ThermalClusterGroup"], super()._missing_(value))


class ThermalCostGeneration(EnumIgnoreCase):
"""
Specifies how to generate thermal cluster cost.
The value `SetManually` is used by default.
"""

SET_MANUALLY = "SetManually"
USE_COST_TIME_SERIES = "useCostTimeseries"


class ThermalProperties(ClusterProperties):
"""
Thermal cluster configuration model.
Expand Down Expand Up @@ -262,6 +272,18 @@ class Thermal860Properties(ThermalProperties):
)


class Thermal870Properties(Thermal860Properties):
"""
Thermal cluster configuration model for study in version 8.7 or above.
"""

costgeneration: ThermalCostGeneration = Field(default=ThermalCostGeneration.SET_MANUALLY)
efficiency: float = Field(default=100.0, ge=0, description="Efficiency (%)")
variableomcost: float = Field(
default=0, description="Operating and Maintenance Cost (€/MWh)"
) # Even if it's a cost it could be negative.


class ThermalConfig(ThermalProperties, IgnoreCaseIdentifier):
"""
Thermal properties with section ID.
Expand All @@ -285,7 +307,7 @@ class ThermalConfig(ThermalProperties, IgnoreCaseIdentifier):

class Thermal860Config(Thermal860Properties, IgnoreCaseIdentifier):
"""
Thermal properties for study in version 8.6 or above.
Thermal properties for study in version 860
Usage:
Expand All @@ -305,9 +327,33 @@ class Thermal860Config(Thermal860Properties, IgnoreCaseIdentifier):
"""


class Thermal870Config(Thermal870Properties, IgnoreCaseIdentifier):
"""
Thermal properties for study in version 8.7 or above.
Usage:
>>> from antarest.study.storage.rawstudy.model.filesystem.config.thermal import Thermal870Config
>>> cl = Thermal870Config(name="cluster 01!", group="Nuclear", co2=123, nh3=456, efficiency=97)
>>> cl.id
'cluster 01'
>>> cl.group == ThermalClusterGroup.NUCLEAR
True
>>> cl.co2
123.0
>>> cl.nh3
456.0
>>> cl.op1
0.0
>>> cl.efficiency
97.0
"""


# NOTE: In the following Union, it is important to place the most specific type first,
# because the type matching generally occurs sequentially from left to right within the union.
ThermalConfigType = t.Union[Thermal860Config, ThermalConfig]
ThermalConfigType = t.Union[Thermal870Config, Thermal860Config, ThermalConfig]


def create_thermal_config(study_version: t.Union[str, int], **kwargs: t.Any) -> ThermalConfigType:
Expand All @@ -325,7 +371,9 @@ def create_thermal_config(study_version: t.Union[str, int], **kwargs: t.Any) ->
ValueError: If the study version is not supported.
"""
version = int(study_version)
if version >= 860:
if version >= 870:
return Thermal870Config(**kwargs)
elif version == 860:
return Thermal860Config(**kwargs)
else:
return ThermalConfig(**kwargs)
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
from antarest.study.storage.rawstudy.model.filesystem.root.input.bindingconstraints.bindingconstraints_ini import (
BindingConstraintsIni,
)
from antarest.study.storage.variantstudy.business.matrix_constants.binding_constraint.series import (
default_bc_hourly,
default_bc_weekly_daily,
from antarest.study.storage.variantstudy.business.matrix_constants.binding_constraint.series_after_v87 import (
default_bc_hourly as default_bc_hourly_87,
)
from antarest.study.storage.variantstudy.business.matrix_constants.binding_constraint.series_after_v87 import (
default_bc_weekly_daily as default_bc_weekly_daily_87,
)
from antarest.study.storage.variantstudy.business.matrix_constants.binding_constraint.series_before_v87 import (
default_bc_hourly as default_bc_hourly_86,
)
from antarest.study.storage.variantstudy.business.matrix_constants.binding_constraint.series_before_v87 import (
default_bc_weekly_daily as default_bc_weekly_daily_86,
)


Expand All @@ -19,23 +27,40 @@ class BindingConstraints(FolderNode):
"""

def build(self) -> TREE:
default_matrices = {
BindingConstraintFrequency.HOURLY: default_bc_hourly,
BindingConstraintFrequency.DAILY: default_bc_weekly_daily,
BindingConstraintFrequency.WEEKLY: default_bc_weekly_daily,
}
children: TREE = {
binding.id: InputSeriesMatrix(
self.context,
self.config.next_file(f"{binding.id}.txt"),
freq=MatrixFrequency(binding.time_step),
nb_columns=3,
default_empty=default_matrices[binding.time_step],
)
for binding in self.config.bindings
}

# noinspection SpellCheckingInspection
cfg = self.config
if cfg.version < 870:
default_matrices = {
BindingConstraintFrequency.HOURLY: default_bc_hourly_86,
BindingConstraintFrequency.DAILY: default_bc_weekly_daily_86,
BindingConstraintFrequency.WEEKLY: default_bc_weekly_daily_86,
}
children: TREE = {
binding.id: InputSeriesMatrix(
self.context,
self.config.next_file(f"{binding.id}.txt"),
freq=MatrixFrequency(binding.time_step),
nb_columns=3,
default_empty=default_matrices[binding.time_step],
)
for binding in self.config.bindings
}
else:
default_matrices = {
BindingConstraintFrequency.HOURLY: default_bc_hourly_87,
BindingConstraintFrequency.DAILY: default_bc_weekly_daily_87,
BindingConstraintFrequency.WEEKLY: default_bc_weekly_daily_87,
}
children = {}
for binding in self.config.bindings:
for term in ["lt", "gt", "eq"]:
matrix_id = f"{binding.id}_{term}"
children[matrix_id] = InputSeriesMatrix(
self.context,
self.config.next_file(f"{matrix_id}.txt"),
freq=MatrixFrequency(binding.time_step),
nb_columns=1 if term in ["lt", "gt"] else None,
default_empty=default_matrices[binding.time_step],
)
children["bindingconstraints"] = BindingConstraintsIni(
self.context, self.config.next_file("bindingconstraints.ini")
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from antarest.study.storage.rawstudy.model.filesystem.common.area_matrix_list import (
AreaMatrixList,
AreaMultipleMatrixList,
BindingConstraintMatrixList,
ThermalMatrixList,
)
from antarest.study.storage.rawstudy.model.filesystem.folder_node import FolderNode
Expand Down Expand Up @@ -41,11 +42,15 @@ class OutputSimulationTsNumbers(FolderNode):
│ ├── ch [...]
│ ├── pompage [...]
│ └── turbinage [...]
└── wind
├── at.txt
├── ch.txt
├── pompage.txt
└── turbinage.txt
├── wind
│ ├── at.txt
│ ├── ch.txt
│ ├── pompage.txt
│ └── turbinage.txt
├── bindingconstraints
├── group_1.txt
├── group_2.txt
└── [...]
"""

def build(self) -> TREE:
Expand Down Expand Up @@ -77,4 +82,8 @@ def build(self) -> TREE:
TsNumbersVector,
),
}
if self.config.version >= 870:
children["bindingconstraints"] = BindingConstraintMatrixList(
self.context, self.config.next_file("bindingconstraints"), matrix_class=TsNumbersVector
)
return children
8 changes: 5 additions & 3 deletions antarest/study/storage/study_upgrader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from http import HTTPStatus
from http.client import HTTPException
from pathlib import Path
from typing import Callable, List, NamedTuple

from antarest.core.exceptions import StudyValidationError

Expand All @@ -19,6 +20,7 @@
from .upgrader_840 import upgrade_840
from .upgrader_850 import upgrade_850
from .upgrader_860 import upgrade_860
from .upgrader_870 import upgrade_870

logger = logging.getLogger(__name__)

Expand All @@ -44,6 +46,7 @@ class UpgradeMethod(t.NamedTuple):
UpgradeMethod("830", "840", upgrade_840, [_GENERAL_DATA_PATH]),
UpgradeMethod("840", "850", upgrade_850, [_GENERAL_DATA_PATH]),
UpgradeMethod("850", "860", upgrade_860, [Path("input"), _GENERAL_DATA_PATH]),
UpgradeMethod("860", "870", upgrade_870, [Path("input/thermal"), Path("input/bindingconstraints")]),
]


Expand Down Expand Up @@ -273,6 +276,5 @@ def should_study_be_denormalized(src_version: str, target_version: str) -> bool:
if curr_version == old and curr_version != target_version:
list_of_upgrades.append(new)
curr_version = new
# For now, the only upgrade that impacts study matrices is the upgrade from v8.1 to v8.2
# In a near future, the upgrade from v8.6 to v8.7 will also require denormalization
return "820" in list_of_upgrades
# These upgrades alter matrices so the study needs to be denormalized
return "820" in list_of_upgrades or "870" in list_of_upgrades
10 changes: 10 additions & 0 deletions antarest/study/storage/study_upgrader/upgrader_860.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@


def upgrade_860(study_path: Path) -> None:
"""
Upgrade the study configuration to version 860.
NOTE:
The file `study.antares` is not upgraded here.
Args:
study_path: path to the study directory.
"""

reader = IniReader(DUPLICATE_KEYS)
data = reader.read(study_path / GENERAL_DATA_PATH)
data["adequacy patch"]["enable-first-step "] = True
Expand Down
Loading

0 comments on commit 0b27f16

Please sign in to comment.