Skip to content

Commit

Permalink
use all optional model to improve code quality / readability (#4)
Browse files Browse the repository at this point in the history
* first draft

* add license header

* continue work

* nicer comment

* also apply to renewables

* continue work for st storages

* use it for hydro

* continue work but 4 tests fail

* fix tests

* little refactor

* Change NonOptional to Default

---------

Co-authored-by: Sigurd Borge <[email protected]>
  • Loading branch information
MartinBelthle and Sigurd-Borge authored Sep 23, 2024
1 parent c658687 commit d1d31d3
Show file tree
Hide file tree
Showing 13 changed files with 387 additions and 574 deletions.
128 changes: 48 additions & 80 deletions src/antares/model/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from antares.model.st_storage import STStorage, STStorageProperties
from antares.model.thermal import ThermalCluster, ThermalClusterProperties
from antares.model.wind import Wind
from antares.tools.all_optional_meta import all_optional_model
from antares.tools.contents_tool import transform_name_to_id, EnumIgnoreCase


Expand All @@ -48,111 +49,78 @@ class AdequacyPatchMode(EnumIgnoreCase):
VIRTUAL = "virtual"


# todo: Warning, with filesystem, we want to avoid camel case and use link_aliasing.
class AreaProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel):
class DefaultAreaProperties(BaseModel, extra="forbid", populate_by_name=True):
"""
DTO for updating area properties
"""

energy_cost_unsupplied: Optional[float] = None
energy_cost_spilled: Optional[float] = None
non_dispatch_power: Optional[bool] = None
dispatch_hydro_power: Optional[bool] = None
other_dispatch_power: Optional[bool] = None
filter_synthesis: Optional[Set[FilterOption]] = None
filter_by_year: Optional[Set[FilterOption]] = None
energy_cost_unsupplied: float = 0.0
energy_cost_spilled: float = 0.0
non_dispatch_power: bool = True
dispatch_hydro_power: bool = True
other_dispatch_power: bool = True
filter_synthesis: Set[FilterOption] = {
FilterOption.HOURLY,
FilterOption.DAILY,
FilterOption.WEEKLY,
FilterOption.MONTHLY,
FilterOption.ANNUAL,
}
filter_by_year: Set[FilterOption] = {
FilterOption.HOURLY,
FilterOption.DAILY,
FilterOption.WEEKLY,
FilterOption.MONTHLY,
FilterOption.ANNUAL,
}
# version 830
adequacy_patch_mode: Optional[AdequacyPatchMode] = None
spread_unsupplied_energy_cost: Optional[float] = None
spread_spilled_energy_cost: Optional[float] = None
adequacy_patch_mode: AdequacyPatchMode = AdequacyPatchMode.OUTSIDE
spread_unsupplied_energy_cost: float = 0.0
spread_spilled_energy_cost: float = 0.0


@all_optional_model
class AreaProperties(DefaultAreaProperties, alias_generator=to_camel):
pass


def config_alias_generator(field_name: str) -> str:
return field_name.replace("_", " ")


# TODO update to use check_if_none
class AreaPropertiesLocal(BaseModel, alias_generator=config_alias_generator):
def __init__(
self,
input_area_properties: AreaProperties = AreaProperties(),
**kwargs: Optional[Any],
):
super().__init__(**kwargs)
self._energy_cost_unsupplied = input_area_properties.energy_cost_unsupplied or 0.0
self._energy_cost_spilled = input_area_properties.energy_cost_spilled or 0.0
self._non_dispatch_power = (
input_area_properties.non_dispatch_power if input_area_properties.non_dispatch_power is not None else True
)
self._dispatch_hydro_power = (
input_area_properties.dispatch_hydro_power
if input_area_properties.dispatch_hydro_power is not None
else True
)
self._other_dispatch_power = (
input_area_properties.other_dispatch_power
if input_area_properties.other_dispatch_power is not None
else True
)
self._filter_synthesis = input_area_properties.filter_synthesis or {
FilterOption.HOURLY,
FilterOption.DAILY,
FilterOption.WEEKLY,
FilterOption.MONTHLY,
FilterOption.ANNUAL,
}
self._filter_by_year = input_area_properties.filter_by_year or {
FilterOption.HOURLY,
FilterOption.DAILY,
FilterOption.WEEKLY,
FilterOption.MONTHLY,
FilterOption.ANNUAL,
}
self._adequacy_patch_mode = (
input_area_properties.adequacy_patch_mode
if input_area_properties.adequacy_patch_mode
else AdequacyPatchMode.OUTSIDE
)
self._spread_spilled_energy_cost = input_area_properties.spread_spilled_energy_cost or 0.0
self._spread_unsupplied_energy_cost = input_area_properties.spread_unsupplied_energy_cost or 0.0

class AreaPropertiesLocal(DefaultAreaProperties, alias_generator=config_alias_generator):
@computed_field # type: ignore[misc]
@property
def nodal_optimization(self) -> Mapping[str, str]:
return {
"non-dispatchable-power": f"{self._non_dispatch_power}".lower(),
"dispatchable-hydro-power": f"{self._dispatch_hydro_power}".lower(),
"other-dispatchable-power": f"{self._other_dispatch_power}".lower(),
"spread-unsupplied-energy-cost": f"{self._spread_unsupplied_energy_cost:.6f}",
"spread-spilled-energy-cost": f"{self._spread_spilled_energy_cost:.6f}",
"average-unsupplied-energy-cost": f"{self._energy_cost_unsupplied:.6f}",
"average-spilled-energy-cost": f"{self._energy_cost_spilled:.6f}",
"non-dispatchable-power": f"{self.non_dispatch_power}".lower(),
"dispatchable-hydro-power": f"{self.dispatch_hydro_power}".lower(),
"other-dispatchable-power": f"{self.other_dispatch_power}".lower(),
"spread-unsupplied-energy-cost": f"{self.spread_unsupplied_energy_cost:.6f}",
"spread-spilled-energy-cost": f"{self.spread_spilled_energy_cost:.6f}",
"average-unsupplied-energy-cost": f"{self.energy_cost_unsupplied:.6f}",
"average-spilled-energy-cost": f"{self.energy_cost_spilled:.6f}",
}

@computed_field # type: ignore[misc]
@property
def filtering(self) -> Mapping[str, str]:
return {
"filter-synthesis": ", ".join(filter_value for filter_value in sort_filter_values(self._filter_synthesis)),
"filter-year-by-year": ", ".join(filter_value for filter_value in sort_filter_values(self._filter_by_year)),
"filter-synthesis": ", ".join(filter_value for filter_value in sort_filter_values(self.filter_synthesis)),
"filter-year-by-year": ", ".join(filter_value for filter_value in sort_filter_values(self.filter_by_year)),
}

def adequacy_patch_mode(self) -> dict[str, dict[str, str]]:
return {"adequacy-patch": {"adequacy-patch-mode": self._adequacy_patch_mode.value}}
def adequacy_patch(self) -> dict[str, dict[str, str]]:
return {"adequacy-patch": {"adequacy-patch-mode": self.adequacy_patch_mode.value}}

def yield_local_dict(self) -> dict[str, Mapping[str, str]]:
args = {"nodal optimization": self.nodal_optimization}
args.update({"filtering": self.filtering})
return args

def yield_area_properties(self) -> AreaProperties:
return AreaProperties(
energy_cost_unsupplied=self._energy_cost_unsupplied,
energy_cost_spilled=self._energy_cost_spilled,
non_dispatch_power=self._non_dispatch_power,
dispatch_hydro_power=self._dispatch_hydro_power,
other_dispatch_power=self._other_dispatch_power,
filter_synthesis=self._filter_synthesis,
filter_by_year=self._filter_by_year,
adequacy_patch_mode=self._adequacy_patch_mode,
spread_unsupplied_energy_cost=self._spread_unsupplied_energy_cost,
spread_spilled_energy_cost=self._spread_spilled_energy_cost,
)
excludes = {"filtering", "nodal_optimization"}
return AreaProperties.model_validate(self.model_dump(mode="json", exclude=excludes))


class AreaUi(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel):
Expand Down
6 changes: 3 additions & 3 deletions src/antares/model/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ class ClusterProperties(BaseModel, extra="forbid", populate_by_name=True, alias_
# Activity status:
# - True: the plant may generate.
# - False: not yet commissioned, moth-balled, etc.
enabled: Optional[bool] = None
enabled: bool = True

unit_count: Optional[int] = None
nominal_capacity: Optional[float] = None
unit_count: int = 1
nominal_capacity: float = 0

@property
def installed_capacity(self) -> Optional[float]:
Expand Down
121 changes: 44 additions & 77 deletions src/antares/model/hydro.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
# This file is part of the Antares project.

from enum import Enum
from typing import Optional, Dict, Any
from typing import Optional, Dict

import pandas as pd
from pydantic import BaseModel, computed_field
from pydantic.alias_generators import to_camel

from antares.tools.ini_tool import check_if_none
from antares.tools.all_optional_meta import all_optional_model


class HydroMatrixName(Enum):
Expand All @@ -32,95 +32,62 @@ class HydroMatrixName(Enum):
COMMON_CREDIT_MODULATIONS = "creditmodulations"


class HydroProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel):
class DefaultHydroProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel):
"""
Properties of hydro system read from the configuration files.
All aliases match the name of the corresponding field in the INI files.
"""

inter_daily_breakdown: Optional[float] = None
intra_daily_modulation: Optional[float] = None
inter_monthly_breakdown: Optional[float] = None
reservoir: Optional[bool] = None
reservoir_capacity: Optional[float] = None
follow_load: Optional[bool] = None
use_water: Optional[bool] = None
hard_bounds: Optional[bool] = None
initialize_reservoir_date: Optional[int] = None
use_heuristic: Optional[bool] = None
power_to_level: Optional[bool] = None
use_leeway: Optional[bool] = None
leeway_low: Optional[float] = None
leeway_up: Optional[float] = None
pumping_efficiency: Optional[float] = None


class HydroPropertiesLocal(BaseModel):
def __init__(
self,
area_id: str,
hydro_properties: Optional[HydroProperties] = None,
**kwargs: Optional[Any],
):
super().__init__(**kwargs)
self._area_id = area_id
hydro_properties = hydro_properties or HydroProperties()
self._inter_daily_breakdown = check_if_none(hydro_properties.inter_daily_breakdown, 1)
self._intra_daily_modulation = check_if_none(hydro_properties.intra_daily_modulation, 24)
self._inter_monthly_breakdown = check_if_none(hydro_properties.inter_monthly_breakdown, 1)
self._reservoir = check_if_none(hydro_properties.reservoir, False)
self._reservoir_capacity = check_if_none(hydro_properties.reservoir_capacity, 0)
self._follow_load = check_if_none(hydro_properties.follow_load, True)
self._use_water = check_if_none(hydro_properties.use_water, False)
self._hard_bounds = check_if_none(hydro_properties.hard_bounds, False)
self._initialize_reservoir_date = check_if_none(hydro_properties.initialize_reservoir_date, 0)
self._use_heuristic = check_if_none(hydro_properties.use_heuristic, True)
self._power_to_level = check_if_none(hydro_properties.power_to_level, False)
self._use_leeway = check_if_none(hydro_properties.use_leeway, False)
self._leeway_low = check_if_none(hydro_properties.leeway_low, 1)
self._leeway_up = check_if_none(hydro_properties.leeway_up, 1)
self._pumping_efficiency = check_if_none(hydro_properties.pumping_efficiency, 1)
inter_daily_breakdown: float = 1
intra_daily_modulation: float = 24
inter_monthly_breakdown: float = 1
reservoir: bool = False
reservoir_capacity: float = 0
follow_load: bool = True
use_water: bool = False
hard_bounds: bool = False
initialize_reservoir_date: int = 0
use_heuristic: bool = True
power_to_level: bool = False
use_leeway: bool = False
leeway_low: float = 1
leeway_up: float = 1
pumping_efficiency: float = 1


@all_optional_model
class HydroProperties(DefaultHydroProperties):
pass


class HydroPropertiesLocal(DefaultHydroProperties):
area_id: str

@computed_field # type: ignore[misc]
@property
def hydro_ini_fields(self) -> dict[str, dict[str, str]]:
return {
"inter-daily-breakdown": {f"{self._area_id}": f"{self._inter_daily_breakdown:.6f}"},
"intra-daily-modulation": {f"{self._area_id}": f"{self._intra_daily_modulation:.6f}"},
"inter-monthly-breakdown": {f"{self._area_id}": f"{self._inter_monthly_breakdown:.6f}"},
"reservoir": {f"{self._area_id}": f"{self._reservoir}".lower()},
"reservoir capacity": {f"{self._area_id}": f"{self._reservoir_capacity:.6f}"},
"follow load": {f"{self._area_id}": f"{self._follow_load}".lower()},
"use water": {f"{self._area_id}": f"{self._use_water}".lower()},
"hard bounds": {f"{self._area_id}": f"{self._hard_bounds}".lower()},
"initialize reservoir date": {f"{self._area_id}": f"{self._initialize_reservoir_date}"},
"use heuristic": {f"{self._area_id}": f"{self._use_heuristic}".lower()},
"power to level": {f"{self._area_id}": f"{self._power_to_level}".lower()},
"use leeway": {f"{self._area_id}": f"{self._use_leeway}".lower()},
"leeway low": {f"{self._area_id}": f"{self._leeway_low:.6f}"},
"leeway up": {f"{self._area_id}": f"{self._leeway_up:.6f}"},
"pumping efficiency": {f"{self._area_id}": f"{self._pumping_efficiency:.6f}"},
"inter-daily-breakdown": {f"{self.area_id}": f"{self.inter_daily_breakdown:.6f}"},
"intra-daily-modulation": {f"{self.area_id}": f"{self.intra_daily_modulation:.6f}"},
"inter-monthly-breakdown": {f"{self.area_id}": f"{self.inter_monthly_breakdown:.6f}"},
"reservoir": {f"{self.area_id}": f"{self.reservoir}".lower()},
"reservoir capacity": {f"{self.area_id}": f"{self.reservoir_capacity:.6f}"},
"follow load": {f"{self.area_id}": f"{self.follow_load}".lower()},
"use water": {f"{self.area_id}": f"{self.use_water}".lower()},
"hard bounds": {f"{self.area_id}": f"{self.hard_bounds}".lower()},
"initialize reservoir date": {f"{self.area_id}": f"{self.initialize_reservoir_date}"},
"use heuristic": {f"{self.area_id}": f"{self.use_heuristic}".lower()},
"power to level": {f"{self.area_id}": f"{self.power_to_level}".lower()},
"use leeway": {f"{self.area_id}": f"{self.use_leeway}".lower()},
"leeway low": {f"{self.area_id}": f"{self.leeway_low:.6f}"},
"leeway up": {f"{self.area_id}": f"{self.leeway_up:.6f}"},
"pumping efficiency": {f"{self.area_id}": f"{self.pumping_efficiency:.6f}"},
}

def yield_hydro_properties(self) -> HydroProperties:
return HydroProperties(
inter_daily_breakdown=self._inter_daily_breakdown,
intra_daily_modulation=self._intra_daily_modulation,
inter_monthly_breakdown=self._inter_monthly_breakdown,
reservoir=self._reservoir,
reservoir_capacity=self._reservoir_capacity,
follow_load=self._follow_load,
use_water=self._use_water,
hard_bounds=self._hard_bounds,
initialize_reservoir_date=self._initialize_reservoir_date,
use_heuristic=self._use_heuristic,
power_to_level=self._power_to_level,
use_leeway=self._use_leeway,
leeway_low=self._leeway_low,
leeway_up=self._leeway_up,
pumping_efficiency=self._pumping_efficiency,
)
excludes = {"area_id", "hydro_ini_fields"}
return HydroProperties.model_validate(self.model_dump(mode="json", exclude=excludes))


class Hydro:
Expand Down
Loading

0 comments on commit d1d31d3

Please sign in to comment.