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(area): move area command creation #2322

Merged
merged 21 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
69febee
fix(area): rename area service
TheoPascoli Jan 28, 2025
0636b9b
feat(area): add a move_area command
TheoPascoli Jan 29, 2025
b8e41b7
fix(area): rename area service
TheoPascoli Jan 28, 2025
4190428
feat(area): add a move_area command
TheoPascoli Jan 29, 2025
6ba034b
Merge remote-tracking branch 'origin/feat/move-area-command-creation'…
TheoPascoli Jan 29, 2025
ea1f96c
feat(area): rename area_name to area_id
TheoPascoli Jan 30, 2025
dc800d7
feat(area): minor fixes
TheoPascoli Jan 30, 2025
c9fcd01
feat(area): extract area objects to area model
TheoPascoli Jan 30, 2025
f8f1570
feat(load): add area integration test
TheoPascoli Jan 30, 2025
2040a93
Merge remote-tracking branch 'origin/dev' into feat/move-area-command…
TheoPascoli Jan 30, 2025
e6b6743
feat(load): minor fix
TheoPascoli Jan 30, 2025
7fbe82e
Merge remote-tracking branch 'origin/dev' into feat/move-area-command…
TheoPascoli Feb 5, 2025
af773bc
Merge remote-tracking branch 'origin/dev' into feat/move-area-command…
TheoPascoli Feb 5, 2025
457d910
fix(area): remove deleted commands method
TheoPascoli Feb 5, 2025
08a8a0d
Merge remote-tracking branch 'origin/dev' into feat/move-area-command…
TheoPascoli Feb 6, 2025
d6b94a6
Merge branch 'dev' into feat/move-area-command-creation
TheoPascoli Feb 6, 2025
a91fb01
feat(area): rename move_area command to update_area_ui
TheoPascoli Feb 6, 2025
1fc1192
feat(area): rename move_area command to update_area_ui
TheoPascoli Feb 6, 2025
7002758
Merge branch 'dev' into feat/move-area-command-creation
TheoPascoli Feb 10, 2025
5c9a22d
fix(area): remove useless default value
TheoPascoli Feb 10, 2025
e1618ed
Merge branch 'dev' into feat/move-area-command-creation
TheoPascoli Feb 10, 2025
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
2 changes: 1 addition & 1 deletion antarest/study/business/allocation_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from typing_extensions import Annotated

from antarest.core.exceptions import AllocationDataNotFound, AreaNotFound
from antarest.study.business.area_management import AreaInfoDTO
from antarest.study.business.model.area_model import AreaInfoDTO
from antarest.study.business.utils import FormFieldsBaseModel, execute_or_add_commands
from antarest.study.model import Study
from antarest.study.storage.storage_service import StudyStorageService
Expand Down
275 changes: 22 additions & 253 deletions antarest/study/business/area_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,27 @@
#
# This file is part of the Antares project.

import enum
import logging
import re
import typing as t

from pydantic import Field

from antarest.core.exceptions import ConfigFileNotFound, DuplicateAreaName, LayerNotAllowedToBeDeleted, LayerNotFound
from antarest.core.model import JSON
from antarest.core.serialization import AntaresBaseModel
from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model
from antarest.study.business.model.area_model import (
AreaCreationDTO,
AreaInfoDTO,
AreaOutput,
AreaType,
ClusterInfoDTO,
LayerInfoDTO,
UpdateAreaUi,
)
from antarest.study.business.utils import execute_or_add_commands
from antarest.study.model import Patch, PatchArea, PatchCluster, RawStudy, Study
from antarest.study.repository import StudyMetadataRepository
from antarest.study.storage.patch_service import PatchService
from antarest.study.storage.rawstudy.model.filesystem.config.area import (
AdequacyPathProperties,
AreaFolder,
OptimizationProperties,
ThermalAreasProperties,
UIProperties,
)
Expand All @@ -37,97 +39,15 @@
from antarest.study.storage.storage_service import StudyStorageService
from antarest.study.storage.variantstudy.model.command.create_area import CreateArea
from antarest.study.storage.variantstudy.model.command.icommand import ICommand
from antarest.study.storage.variantstudy.model.command.move_area import MoveArea
from antarest.study.storage.variantstudy.model.command.remove_area import RemoveArea
from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig

logger = logging.getLogger(__name__)


class AreaType(enum.Enum):
AREA = "AREA"
DISTRICT = "DISTRICT"


class AreaCreationDTO(AntaresBaseModel):
name: str
type: AreaType
metadata: t.Optional[PatchArea] = None
set: t.Optional[t.List[str]] = None


# review: is this class necessary?
class ClusterInfoDTO(PatchCluster):
id: str
name: str
enabled: bool = True
unitcount: int = 0
nominalcapacity: float = 0
group: t.Optional[str] = None
min_stable_power: t.Optional[float] = None
min_up_time: t.Optional[int] = None
min_down_time: t.Optional[int] = None
spinning: t.Optional[float] = None
marginal_cost: t.Optional[float] = None
spread_cost: t.Optional[float] = None
market_bid_cost: t.Optional[float] = None


class AreaInfoDTO(AreaCreationDTO):
id: str
thermals: t.Optional[t.List[ClusterInfoDTO]] = None


class LayerInfoDTO(AntaresBaseModel):
id: str
name: str
areas: t.List[str]


class UpdateAreaUi(AntaresBaseModel, extra="forbid", populate_by_name=True):
"""
DTO for updating area UI

Usage:

>>> from antarest.study.business.area_management import UpdateAreaUi
>>> from pprint import pprint

>>> obj = {
... "x": -673.75,
... "y": 301.5,
... "color_rgb": [230, 108, 44],
... "layerX": {"0": -230, "4": -230, "6": -95, "7": -230, "8": -230},
... "layerY": {"0": 136, "4": 136, "6": 39, "7": 136, "8": 136},
... "layerColor": {
... "0": "230, 108, 44",
... "4": "230, 108, 44",
... "6": "230, 108, 44",
... "7": "230, 108, 44",
... "8": "230, 108, 44",
... },
... }

>>> model = UpdateAreaUi(**obj)
>>> pprint(model.model_dump(by_alias=True), width=80)
{'colorRgb': [230, 108, 44],
'layerColor': {0: '230, 108, 44',
4: '230, 108, 44',
6: '230, 108, 44',
7: '230, 108, 44',
8: '230, 108, 44'},
'layerX': {0: -230, 4: -230, 6: -95, 7: -230, 8: -230},
'layerY': {0: 136, 4: 136, 6: 39, 7: 136, 8: 136},
'x': -673,
'y': 301}

"""

x: int = Field(title="X position")
y: int = Field(title="Y position")
color_rgb: t.Sequence[int] = Field(title="RGB color", alias="colorRgb")
layer_x: t.Mapping[int, int] = Field(default_factory=dict, title="X position of each layer", alias="layerX")
layer_y: t.Mapping[int, int] = Field(default_factory=dict, title="Y position of each layer", alias="layerY")
layer_color: t.Mapping[int, str] = Field(default_factory=dict, title="Color of each layer", alias="layerColor")
_ALL_AREAS_PATH = "input/areas"
_THERMAL_AREAS_PATH = "input/thermal/areas"


def _get_ui_info_map(file_study: FileStudy, area_ids: t.Sequence[str]) -> t.Dict[str, t.Any]:
Expand Down Expand Up @@ -171,101 +91,6 @@ def _get_area_layers(area_uis: t.Dict[str, t.Any], area: str) -> t.List[str]:
return []


_ALL_AREAS_PATH = "input/areas"
_THERMAL_AREAS_PATH = "input/thermal/areas"


# noinspection SpellCheckingInspection
class _BaseAreaDTO(
OptimizationProperties.FilteringSection,
OptimizationProperties.ModalOptimizationSection,
AdequacyPathProperties.AdequacyPathSection,
extra="forbid",
validate_assignment=True,
populate_by_name=True,
):
"""
Represents an area output.

Aggregates the fields of the `OptimizationProperties` and `AdequacyPathProperties` classes,
but without the `UIProperties` fields.

Add the fields extracted from the `/input/thermal/areas.ini` information:

- `average_unsupplied_energy_cost` is extracted from `unserverd_energy_cost`,
- `average_spilled_energy_cost` is extracted from `spilled_energy_cost`.
"""

average_unsupplied_energy_cost: float = Field(0.0, description="average unserverd energy cost (€/MWh)")
average_spilled_energy_cost: float = Field(0.0, description="average spilled energy cost (€/MWh)")


# noinspection SpellCheckingInspection
@all_optional_model
@camel_case_model
class AreaOutput(_BaseAreaDTO):
"""
DTO object use to get the area information using a flat structure.
"""

@classmethod
def from_model(
cls,
area_folder: AreaFolder,
*,
average_unsupplied_energy_cost: float,
average_spilled_energy_cost: float,
) -> "AreaOutput":
"""
Creates a `GetAreaDTO` object from configuration data.

Args:
area_folder: Configuration data read from the `/input/areas/<area>` information.
average_unsupplied_energy_cost: Unserverd energy cost (€/MWh).
average_spilled_energy_cost: Spilled energy cost (€/MWh).
Returns:
The `GetAreaDTO` object.
"""
obj = {
"average_unsupplied_energy_cost": average_unsupplied_energy_cost,
"average_spilled_energy_cost": average_spilled_energy_cost,
**area_folder.optimization.filtering.model_dump(mode="json", by_alias=False),
**area_folder.optimization.nodal_optimization.model_dump(mode="json", by_alias=False),
# adequacy_patch is only available if study version >= 830.
**(
area_folder.adequacy_patch.adequacy_patch.model_dump(mode="json", by_alias=False)
if area_folder.adequacy_patch
else {}
),
}
return cls(**obj)

def _to_optimization(self) -> OptimizationProperties:
obj = {name: getattr(self, name) for name in OptimizationProperties.FilteringSection.model_fields}
filtering_section = OptimizationProperties.FilteringSection(**obj)
obj = {name: getattr(self, name) for name in OptimizationProperties.ModalOptimizationSection.model_fields}
nodal_optimization_section = OptimizationProperties.ModalOptimizationSection(**obj)
args = {"filtering": filtering_section, "nodal_optimization": nodal_optimization_section}
return OptimizationProperties.model_validate(args)

def _to_adequacy_patch(self) -> t.Optional[AdequacyPathProperties]:
obj = {name: getattr(self, name) for name in AdequacyPathProperties.AdequacyPathSection.model_fields}
# If all fields are `None`, the object is empty.
if all(value is None for value in obj.values()):
return None
adequacy_path_section = AdequacyPathProperties.AdequacyPathSection(**obj)
return AdequacyPathProperties.model_validate({"adequacy_patch": adequacy_path_section})

@property
def area_folder(self) -> AreaFolder:
area_folder = AreaFolder(
optimization=self._to_optimization(),
adequacy_patch=self._to_adequacy_patch(),
# UI properties are not configurable in Table Mode
)
return area_folder


class AreaManager:
"""
Manages operations related to areas in a study, including retrieval, creation, and updates.
Expand Down Expand Up @@ -414,7 +239,7 @@ def update_areas_props(

@staticmethod
def get_table_schema() -> JSON:
return AreaOutput.schema()
return AreaOutput.model_json_schema()

def get_all_areas(self, study: RawStudy, area_type: t.Optional[AreaType] = None) -> t.List[AreaInfoDTO]:
"""
Expand Down Expand Up @@ -680,73 +505,17 @@ def update_area_metadata(
)

def update_area_ui(self, study: Study, area_id: str, area_ui: UpdateAreaUi, layer: str = "0") -> None:
TheoPascoli marked this conversation as resolved.
Show resolved Hide resolved
obj = {
"x": area_ui.x,
"y": area_ui.y,
"color_r": area_ui.color_rgb[0],
"color_g": area_ui.color_rgb[1],
"color_b": area_ui.color_rgb[2],
}
file_study = self.storage_service.get_storage(study).get_raw(study)
commands = (
[
UpdateConfig(
target=f"input/areas/{area_id}/ui/ui/x",
data=obj["x"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
UpdateConfig(
target=f"input/areas/{area_id}/ui/ui/y",
data=obj["y"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
UpdateConfig(
target=f"input/areas/{area_id}/ui/ui/color_r",
data=obj["color_r"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
UpdateConfig(
target=f"input/areas/{area_id}/ui/ui/color_g",
data=obj["color_g"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
UpdateConfig(
target=f"input/areas/{area_id}/ui/ui/color_b",
data=obj["color_b"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
]
if layer == "0"
else []
)
commands.extend(
[
UpdateConfig(
target=f"input/areas/{area_id}/ui/layerX/{layer}",
data=obj["x"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
UpdateConfig(
target=f"input/areas/{area_id}/ui/layerY/{layer}",
data=obj["y"],
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
UpdateConfig(
target=f"input/areas/{area_id}/ui/layerColor/{layer}",
data=f"{obj['color_r']},{obj['color_g']},{obj['color_b']}",
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
),
]

command = MoveArea(
area_id=area_id,
area_ui=area_ui,
layer=layer,
command_context=self.storage_service.variant_study_service.command_factory.command_context,
study_version=file_study.config.version,
)
execute_or_add_commands(study, file_study, commands, self.storage_service)

execute_or_add_commands(study, file_study, [command], self.storage_service)

def update_thermal_cluster_metadata(
self,
Expand Down
2 changes: 1 addition & 1 deletion antarest/study/business/correlation_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from pydantic import ValidationInfo, field_validator

from antarest.core.exceptions import AreaNotFound
from antarest.study.business.area_management import AreaInfoDTO
from antarest.study.business.model.area_model import AreaInfoDTO
from antarest.study.business.utils import FormFieldsBaseModel, execute_or_add_commands
from antarest.study.model import Study
from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy
Expand Down
Loading
Loading