Skip to content

Commit

Permalink
feat(simulator): API change to add support for study version 8.8 (#2006)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBelthle authored Apr 16, 2024
1 parent e7cb3aa commit 33ae79a
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 58 deletions.
57 changes: 28 additions & 29 deletions antarest/study/business/areas/st_storage_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
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.st_storage import (
STStorageConfig,
STStorage880Config,
STStorage880Properties,
STStorageConfigType,
STStorageGroup,
STStorageProperties,
create_st_storage_config,
)
from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy
Expand All @@ -32,18 +33,9 @@
from antarest.study.storage.variantstudy.model.command.replace_matrix import ReplaceMatrix
from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig

__all__ = (
"STStorageManager",
"STStorageCreation",
"STStorageInput",
"STStorageOutput",
"STStorageMatrix",
"STStorageTimeSeries",
)


@camel_case_model
class STStorageInput(STStorageProperties, metaclass=AllOptionalMetaclass, use_none=True):
class STStorageInput(STStorage880Properties, metaclass=AllOptionalMetaclass, use_none=True):
"""
Model representing the form used to EDIT an existing short-term storage.
"""
Expand Down Expand Up @@ -79,13 +71,13 @@ def validate_name(cls, name: t.Optional[str]) -> str:
return name

# noinspection PyUnusedLocal
def to_config(self, study_version: t.Union[str, int]) -> STStorageConfig:
def to_config(self, study_version: t.Union[str, int]) -> STStorageConfigType:
values = self.dict(by_alias=False, exclude_none=True)
return STStorageConfig(**values)
return create_st_storage_config(study_version=study_version, **values)


@camel_case_model
class STStorageOutput(STStorageConfig):
class STStorageOutput(STStorage880Config):
"""
Model representing the form used to display the details of a short-term storage entry.
"""
Expand All @@ -104,12 +96,6 @@ def schema_extra(schema: t.MutableMapping[str, t.Any]) -> None:
initial_level_optim=True,
)

@classmethod
def from_config(cls, storage_id: str, config: t.Mapping[str, t.Any]) -> "STStorageOutput":
storage = STStorageConfig(**config, id=storage_id)
values = storage.dict(by_alias=False)
return cls(**values)


# =============
# Time series
Expand Down Expand Up @@ -241,6 +227,16 @@ def _get_values_by_ids(file_study: FileStudy, area_id: str) -> t.Mapping[str, t.
raise STStorageConfigNotFound(path, area_id) from None


def create_storage_output(
study_version: t.Union[str, int],
cluster_id: str,
config: t.Mapping[str, t.Any],
) -> "STStorageOutput":
obj = create_st_storage_config(study_version=study_version, **config, id=cluster_id)
kwargs = obj.dict(by_alias=False)
return STStorageOutput(**kwargs)


class STStorageManager:
"""
Manage short-term storage configuration in a study
Expand Down Expand Up @@ -291,7 +287,7 @@ def create_storage(
output = self.get_storage(study, area_id, storage_id=storage.id)
return output

def _make_create_cluster_cmd(self, area_id: str, cluster: STStorageConfig) -> CreateSTStorage:
def _make_create_cluster_cmd(self, area_id: str, cluster: STStorageConfigType) -> CreateSTStorage:
command = CreateSTStorage(
area_id=area_id,
parameters=cluster,
Expand Down Expand Up @@ -326,11 +322,9 @@ def get_storages(

# Sort STStorageConfig by groups and then by name
order_by = operator.attrgetter("group", "name")
all_configs = sorted(
(STStorageConfig(id=storage_id, **options) for storage_id, options in config.items()),
key=order_by,
)
return tuple(STStorageOutput(**config.dict(by_alias=False)) for config in all_configs)
study_version = int(study.version)
storages = [create_storage_output(study_version, storage_id, options) for storage_id, options in config.items()]
return sorted(storages, key=order_by)

def get_storage(
self,
Expand All @@ -356,7 +350,7 @@ def get_storage(
config = file_study.tree.get(path.split("/"), depth=1)
except KeyError:
raise STStorageNotFound(path, storage_id) from None
return STStorageOutput.from_config(storage_id, config)
return create_storage_output(int(study.version), storage_id, config)

def update_storage(
self,
Expand Down Expand Up @@ -469,7 +463,12 @@ def duplicate_cluster(self, study: Study, area_id: str, source_id: str, new_clus
# Cluster duplication
current_cluster = self.get_storage(study, area_id, source_id)
current_cluster.name = new_cluster_name
creation_form = STStorageCreation(**current_cluster.dict(by_alias=False, exclude={"id"}))
fields_to_exclude = {"id"}
# We should remove the field 'enabled' for studies before v8.8 as it didn't exist
if int(study.version) < 880:
fields_to_exclude.add("enabled")
creation_form = STStorageCreation(**current_cluster.dict(by_alias=False, exclude=fields_to_exclude))

new_config = creation_form.to_config(study.version)
create_cluster_cmd = self._make_create_cluster_cmd(area_id, new_config)

Expand Down
2 changes: 1 addition & 1 deletion antarest/study/business/thematic_trimming_field_infos.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class ThematicTrimmingFormFields(FormFieldsBaseModel, metaclass=AllOptionalMetac
"sts_inj_by_plant": {"topic": _SHORT_TERM_STORAGES, "path": "STS inj by plant", "default_value": True, "start_version": 860},
"sts_withdrawal_by_plant": {"topic": _SHORT_TERM_STORAGES, "path": "STS withdrawal by plant", "default_value": True, "start_version": 860},
"sts_lvl_by_plant": {"topic": _SHORT_TERM_STORAGES, "path": "STS lvl by plant", "default_value": True, "start_version": 860},
"sts_cashflow_by_cluster": {"topic": _SHORT_TERM_STORAGES, "path": "STS Cashflow By Cluster", "default_value": True, "start_version": 860},
"sts_cashflow_by_cluster": {"topic": _SHORT_TERM_STORAGES, "path": "STS Cashflow By Cluster", "default_value": True, "start_version": 880},
# topic: "Short-Term Storages - Group"
"psp_open_injection": {"topic": _SHORT_TERM_STORAGES_GROUP, "path": "PSP_open_injection", "default_value": True, "start_version": 860},
"psp_open_withdrawal": {"topic": _SHORT_TERM_STORAGES_GROUP, "path": "PSP_open_withdrawal", "default_value": True, "start_version": 860},
Expand Down
3 changes: 2 additions & 1 deletion antarest/study/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@
"850": "empty_study_850.zip",
"860": "empty_study_860.zip",
"870": "empty_study_870.zip",
"880": "empty_study_880.zip",
}

NEW_DEFAULT_STUDY_VERSION: str = "870"
NEW_DEFAULT_STUDY_VERSION: str = "880"


class StudyGroup(Base): # type:ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@
from antarest.study.storage.rawstudy.model.filesystem.config.cluster import ItemProperties
from antarest.study.storage.rawstudy.model.filesystem.config.identifier import LowerCaseIdentifier

__all__ = (
"STStorageGroup",
"STStorageProperties",
"STStorageConfig",
"STStorageConfigType",
"create_st_storage_config",
)


class STStorageGroup(EnumIgnoreCase):
"""
Expand Down Expand Up @@ -75,7 +67,7 @@ class STStorageProperties(ItemProperties):
ge=0,
le=1,
)
# The `initial_level` value must be between 0 and 1, but the default value is 0.
# The `initial_level` value must be between 0 and 1, but the default value is 0.5
initial_level: float = Field(
0.5,
description="Initial level of the storage system (%)",
Expand All @@ -90,6 +82,17 @@ class STStorageProperties(ItemProperties):
)


class STStorage880Properties(STStorageProperties):
"""
Short term storage configuration model for 880 study.
"""

# Activity status:
# - True: the plant may generate.
# - False: Ignored by the simulator.
enabled: bool = Field(default=True, description="Activity status")


# noinspection SpellCheckingInspection
class STStorageConfig(STStorageProperties, LowerCaseIdentifier):
"""
Expand All @@ -116,7 +119,27 @@ class STStorageConfig(STStorageProperties, LowerCaseIdentifier):
"""


STStorageConfigType = STStorageConfig
class STStorage880Config(STStorage880Properties, LowerCaseIdentifier):
"""
Short Term Storage properties for study in version 8.8 or above.
Usage:
>>> from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorage880Config
>>> st = STStorage880Config(name="Storage 1", group="battery", enabled=False)
>>> st.id
'storage 1'
>>> st.group == STStorageGroup.BATTERY
True
>>> st.enabled
False
"""


# NOTE: In the following Union, it is important to place the older version first,
# because otherwise, creating a short term storage always creates a v8.8 one.
STStorageConfigType = t.Union[STStorageConfig, STStorage880Config]


def create_st_storage_config(study_version: t.Union[str, int], **kwargs: t.Any) -> STStorageConfigType:
Expand All @@ -136,4 +159,6 @@ def create_st_storage_config(study_version: t.Union[str, int], **kwargs: t.Any)
version = int(study_version)
if version < 860:
raise ValueError(f"Unsupported study version: {version}")
return STStorageConfig(**kwargs)
elif version < 880:
return STStorageConfig(**kwargs)
return STStorage880Config(**kwargs)
22 changes: 15 additions & 7 deletions antarest/study/storage/study_upgrader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
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

from .upgrader_710 import upgrade_710
from .upgrader_720 import upgrade_720
from .upgrader_800 import upgrade_800
Expand All @@ -21,6 +19,12 @@
from .upgrader_850 import upgrade_850
from .upgrader_860 import upgrade_860
from .upgrader_870 import upgrade_870
from .upgrader_880 import upgrade_880

STUDY_ANTARES = "study.antares"
"""
Main file of an Antares study containing the caption, the version, the creation date, etc.
"""

logger = logging.getLogger(__name__)

Expand All @@ -47,6 +51,7 @@ class UpgradeMethod(t.NamedTuple):
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")]),
UpgradeMethod("870", "880", upgrade_880, [Path("input/st-storage/clusters")]),
]


Expand Down Expand Up @@ -105,7 +110,7 @@ def get_current_version(study_path: Path) -> str:
`study.antares` file or does not match the expected format.
"""

antares_path = study_path / "study.antares"
antares_path = study_path / STUDY_ANTARES
pattern = r"version\s*=\s*([\w.-]+)\s*"
with antares_path.open(encoding="utf-8") as lines:
for line in lines:
Expand Down Expand Up @@ -163,8 +168,8 @@ def can_upgrade_version(from_version: str, to_version: str) -> t.List[Path]:


def _update_study_antares_file(target_version: str, study_path: Path) -> None:
file = study_path / "study.antares"
content = file.read_text(encoding="utf-8")
antares_path = study_path / STUDY_ANTARES
content = antares_path.read_text(encoding="utf-8")
content = re.sub(
r"^version\s*=.*$",
f"version = {target_version}",
Expand All @@ -177,7 +182,7 @@ def _update_study_antares_file(target_version: str, study_path: Path) -> None:
content,
flags=re.MULTILINE,
)
file.write_text(content, encoding="utf-8")
antares_path.write_text(content, encoding="utf-8")


def _copies_only_necessary_files(files_to_upgrade: t.List[Path], study_path: Path, tmp_path: Path) -> t.List[Path]:
Expand All @@ -192,10 +197,13 @@ def _copies_only_necessary_files(files_to_upgrade: t.List[Path], study_path: Pat
without any children that has parents already in the list.
"""
files_to_copy = _filters_out_children_files(files_to_upgrade)
files_to_copy.append(Path("study.antares"))
files_to_copy.append(Path(STUDY_ANTARES))
files_to_retrieve = []
for path in files_to_copy:
entire_path = study_path / path
if not entire_path.exists():
# This can happen when upgrading a study to v8.8.
continue
if entire_path.is_dir():
if not (tmp_path / path).exists():
shutil.copytree(entire_path, tmp_path / path, dirs_exist_ok=True)
Expand Down
32 changes: 32 additions & 0 deletions antarest/study/storage/study_upgrader/upgrader_880.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import glob
from pathlib import Path

from antarest.study.storage.rawstudy.ini_reader import IniReader
from antarest.study.storage.rawstudy.ini_writer import IniWriter
from antarest.study.storage.rawstudy.model.filesystem.root.settings.generaldata import DUPLICATE_KEYS


# noinspection SpellCheckingInspection
def upgrade_880(study_path: Path) -> None:
"""
Upgrade the study configuration to version 880.
NOTE:
The file `study.antares` is not upgraded here.
Args:
study_path: path to the study directory.
"""
st_storage_path = study_path / "input" / "st-storage" / "clusters"
if not st_storage_path.exists():
# The folder only exists for studies in v8.6+ that have some short term storage clusters.
# For every other case, this upgrader has nothing to do.
return
writer = IniWriter(special_keys=DUPLICATE_KEYS)
cluster_files = glob.glob(str(st_storage_path / "*" / "list.ini"))
for file in cluster_files:
file_path = Path(file)
cluster_list = IniReader().read(file_path)
for cluster in cluster_list:
cluster_list[cluster]["enabled"] = True
writer.write(cluster_list, file_path)
Binary file added resources/empty_study_880.zip
Binary file not shown.
Loading

0 comments on commit 33ae79a

Please sign in to comment.