Skip to content

Commit

Permalink
refactor(settings): refactor settings to create an user oriented class (
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBelthle authored Feb 7, 2025
1 parent 11ebf94 commit a15b685
Show file tree
Hide file tree
Showing 28 changed files with 1,340 additions and 1,983 deletions.
6 changes: 6 additions & 0 deletions src/antares/craft/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ def __init__(self, study_name: str, message: str) -> None:
super().__init__(self.message)


class StudySettingsReadError(Exception):
def __init__(self, study_name: str, message: str) -> None:
self.message = f"Could not read settings for study {study_name}: " + message
super().__init__(self.message)


class StudyDeletionError(Exception):
def __init__(self, study_id: str, message: str) -> None:
self.message = f"Could not delete the study {study_id}: " + message
Expand Down
61 changes: 13 additions & 48 deletions src/antares/craft/model/settings/adequacy_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,59 +9,24 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.

from dataclasses import dataclass
from enum import Enum

from antares.craft.tools.all_optional_meta import all_optional_model
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from typing import Optional


class PriceTakingOrder(Enum):
DENS = "DENS"
LOAD = "Load"


class DefaultAdequacyPatchParameters(BaseModel, populate_by_name=True, alias_generator=to_camel):
model_config = ConfigDict(use_enum_values=True)

# version 830
enable_adequacy_patch: bool = False
ntc_from_physical_areas_out_to_physical_areas_in_adequacy_patch: bool = True
ntc_between_physical_areas_out_adequacy_patch: bool = True
# version 850
price_taking_order: PriceTakingOrder = Field(default=PriceTakingOrder.DENS, validate_default=True)
include_hurdle_cost_csr: bool = False
check_csr_cost_function: bool = False
enable_first_step: bool = False
threshold_initiate_curtailment_sharing_rule: int = 0
threshold_display_local_matching_rule_violations: int = 0
threshold_csr_variable_bounds_relaxation: int = 3


@all_optional_model
class AdequacyPatchParameters(DefaultAdequacyPatchParameters):
pass


class AdequacyPatchParametersLocal(DefaultAdequacyPatchParameters):
@property
def ini_fields(self) -> dict:
return {
"adequacy patch": {
"include-adq-patch": str(self.enable_adequacy_patch).lower(),
"set-to-null-ntc-from-physical-out-to-physical-in-for-first-step": str(
self.ntc_from_physical_areas_out_to_physical_areas_in_adequacy_patch
).lower(),
"set-to-null-ntc-between-physical-out-for-first-step": str(
self.ntc_between_physical_areas_out_adequacy_patch
).lower(),
"enable-first-step": str(self.enable_first_step).lower(),
"price-taking-order": self.price_taking_order,
"include-hurdle-cost-csr": str(self.include_hurdle_cost_csr).lower(),
"check-csr-cost-function": str(self.check_csr_cost_function).lower(),
"threshold-initiate-curtailment-sharing-rule": f"{self.threshold_initiate_curtailment_sharing_rule:.6f}",
"threshold-display-local-matching-rule-violations": f"{self.threshold_display_local_matching_rule_violations:.6f}",
"threshold-csr-variable-bounds-relaxation": f"{self.threshold_csr_variable_bounds_relaxation}",
}
}
@dataclass
class AdequacyPatchParameters:
include_adq_patch: Optional[bool] = None
set_to_null_ntc_from_physical_out_to_physical_in_for_first_step: Optional[bool] = None
set_to_null_ntc_between_physical_out_for_first_step: Optional[bool] = None
price_taking_order: Optional[PriceTakingOrder] = None
include_hurdle_cost_csr: Optional[bool] = None
check_csr_cost_function: Optional[bool] = None
threshold_initiate_curtailment_sharing_rule: Optional[int] = None
threshold_display_local_matching_rule_violations: Optional[int] = None
threshold_csr_variable_bounds_relaxation: Optional[int] = None
111 changes: 23 additions & 88 deletions src/antares/craft/model/settings/advanced_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.

from dataclasses import dataclass
from enum import Enum
from typing import Any, Optional
from typing import Optional

from antares.craft.model.settings.general import OutputChoices
from antares.craft.tools.alias_generators import to_kebab
from antares.craft.tools.all_optional_meta import all_optional_model
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.alias_generators import to_camel
from typing_extensions import Self


class InitialReservoirLevel(Enum):
Expand Down Expand Up @@ -47,10 +42,6 @@ class SheddingPolicy(Enum):
MINIMIZE_DURATION = "minimize duration"


class ReserveManagement(Enum):
GLOBAL = "global"


class UnitCommitmentMode(Enum):
FAST = "fast"
ACCURATE = "accurate"
Expand All @@ -70,81 +61,25 @@ class RenewableGenerationModeling(Enum):
CLUSTERS = "clusters"


class DefaultAdvancedParameters(BaseModel, alias_generator=to_camel):
model_config = ConfigDict(use_enum_values=True)

# Advanced parameters
@dataclass
class AdvancedParameters:
initial_reservoir_levels: Optional[InitialReservoirLevel] = None
hydro_heuristic_policy: Optional[HydroHeuristicPolicy] = None
hydro_pricing_mode: Optional[HydroPricingMode] = None
power_fluctuations: Optional[PowerFluctuation] = None
shedding_policy: Optional[SheddingPolicy] = None
unit_commitment_mode: Optional[UnitCommitmentMode] = None
number_of_cores_mode: Optional[SimulationCore] = None
renewable_generation_modelling: Optional[RenewableGenerationModeling] = None
accuracy_on_correlation: Optional[set[OutputChoices]] = None
# Other preferences
initial_reservoir_levels: InitialReservoirLevel = Field(
default=InitialReservoirLevel.COLD_START, validate_default=True
)
hydro_heuristic_policy: HydroHeuristicPolicy = Field(
default=HydroHeuristicPolicy.ACCOMMODATE_RULES_CURVES, validate_default=True
)
hydro_pricing_mode: HydroPricingMode = Field(default=HydroPricingMode.FAST, validate_default=True)
power_fluctuations: PowerFluctuation = Field(default=PowerFluctuation.FREE_MODULATIONS, validate_default=True)
shedding_policy: SheddingPolicy = Field(default=SheddingPolicy.SHAVE_PEAKS, validate_default=True)
unit_commitment_mode: UnitCommitmentMode = Field(default=UnitCommitmentMode.FAST, validate_default=True)
number_of_cores_mode: SimulationCore = Field(default=SimulationCore.MEDIUM, validate_default=True)
renewable_generation_modelling: RenewableGenerationModeling = Field(
default=RenewableGenerationModeling.AGGREGATED, validate_default=True
)
# Seeds
seed_tsgen_wind: int = 5489
seed_tsgen_load: int = 1005489
seed_tsgen_hydro: int = 2005489
seed_tsgen_thermal: int = 3005489
seed_tsgen_solar: int = 4005489
seed_tsnumbers: int = 5005489
seed_unsupplied_energy_costs: int = 6005489
seed_spilled_energy_costs: int = 7005489
seed_thermal_costs: int = 8005489
seed_hydro_costs: int = 9005489
seed_initial_reservoir_levels: int = 10005489


@all_optional_model
class AdvancedParameters(DefaultAdvancedParameters):
@model_validator(mode="before")
def change_accuracy_on_correlation(cls, data: Any) -> Self:
if "accuracyOnCorrelation" in data.keys():
data["accuracyOnCorrelation"] = (
{OutputChoices(list_item) for list_item in data["accuracyOnCorrelation"].replace(" ", "").split(",")}
if data["accuracyOnCorrelation"]
else None
)
return data


class AdvancedParametersLocal(DefaultAdvancedParameters, alias_generator=to_kebab):
@property
def ini_fields(self) -> dict:
return {
"other preferences": {
"initial-reservoir-levels": self.initial_reservoir_levels,
"hydro-heuristic-policy": self.hydro_heuristic_policy,
"hydro-pricing-mode": self.hydro_pricing_mode,
"power-fluctuations": self.power_fluctuations,
"shedding-policy": self.shedding_policy,
"unit-commitment-mode": self.unit_commitment_mode,
"number-of-cores-mode": self.number_of_cores_mode,
"renewable-generation-modelling": self.renewable_generation_modelling,
},
"advanced parameters": {
"accuracy-on-correlation": self.accuracy_on_correlation if self.accuracy_on_correlation else "",
},
"seeds - Mersenne Twister": {
"seed-tsgen-wind": str(self.seed_tsgen_wind),
"seed-tsgen-load": str(self.seed_tsgen_load),
"seed-tsgen-hydro": str(self.seed_tsgen_hydro),
"seed-tsgen-thermal": str(self.seed_tsgen_thermal),
"seed-tsgen-solar": str(self.seed_tsgen_solar),
"seed-tsnumbers": str(self.seed_tsnumbers),
"seed-unsupplied-energy-costs": str(self.seed_unsupplied_energy_costs),
"seed-spilled-energy-costs": str(self.seed_spilled_energy_costs),
"seed-thermal-costs": str(self.seed_thermal_costs),
"seed-hydro-costs": str(self.seed_hydro_costs),
"seed-initial-reservoir-levels": str(self.seed_initial_reservoir_levels),
},
}


@dataclass
class SeedParameters:
seed_tsgen_thermal: Optional[int] = None
seed_tsnumbers: Optional[int] = None
seed_unsupplied_energy_costs: Optional[int] = None
seed_spilled_energy_costs: Optional[int] = None
seed_thermal_costs: Optional[int] = None
seed_hydro_costs: Optional[int] = None
seed_initial_reservoir_levels: Optional[int] = None
140 changes: 42 additions & 98 deletions src/antares/craft/model/settings/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,40 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
from dataclasses import dataclass
from typing import Optional

import typing as t

from antares.craft.tools.all_optional_meta import all_optional_model
from antares.craft.tools.contents_tool import EnumIgnoreCase
from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic.alias_generators import to_camel


class Mode(EnumIgnoreCase):
ECONOMY = "economy"
ADEQUACY = "adequacy"
DRAFT = "draft"
ECONOMY = "Economy"
ADEQUACY = "Adequacy"


class Month(EnumIgnoreCase):
JANUARY = "january"
FEBRUARY = "february"
MARCH = "march"
APRIL = "april"
MAY = "may"
JUNE = "june"
JULY = "july"
AUGUST = "august"
SEPTEMBER = "september"
OCTOBER = "october"
NOVEMBER = "november"
DECEMBER = "december"
JANUARY = "January"
FEBRUARY = "February"
MARCH = "March"
APRIL = "April"
MAY = "May"
JUNE = "June"
JULY = "July"
AUGUST = "August"
SEPTEMBER = "September"
OCTOBER = "October"
NOVEMBER = "November"
DECEMBER = "December"


class WeekDay(EnumIgnoreCase):
MONDAY = "monday"
TUESDAY = "tuesday"
WEDNESDAY = "wednesday"
THURSDAY = "thursday"
FRIDAY = "friday"
SATURDAY = "saturday"
SUNDAY = "sunday"
MONDAY = "Monday"
TUESDAY = "Tuesday"
WEDNESDAY = "Wednesday"
THURSDAY = "Thursday"
FRIDAY = "Friday"
SATURDAY = "Saturday"
SUNDAY = "Sunday"


class BuildingMode(EnumIgnoreCase):
Expand All @@ -71,74 +67,22 @@ class OutputFormat(EnumIgnoreCase):
ZIP = "zip-files"


class DefaultGeneralParameters(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel):
model_config = ConfigDict(use_enum_values=True)

mode: Mode = Field(default=Mode.ECONOMY, validate_default=True)
horizon: str = ""
# Calendar parameters
nb_years: int = 1
first_day: int = 1
last_day: int = 365
first_january: WeekDay = Field(default=WeekDay.MONDAY, validate_default=True)
first_month: Month = Field(default=Month.JANUARY, validate_default=True)
first_week_day: WeekDay = Field(default=WeekDay.MONDAY, validate_default=True)
leap_year: bool = False
# Additional parameters
year_by_year: bool = False
building_mode: BuildingMode = Field(
default=BuildingMode.AUTOMATIC, validate_default=True
) # ? derated and custom-scenario
selection_mode: bool = False # ? user-playlist
thematic_trimming: bool = False
geographic_trimming: bool = False
active_rules_scenario: str = "default ruleset" # only one option available currently
read_only: bool = False
# Output parameters
simulation_synthesis: bool = True # ? output/synthesis
mc_scenario: bool = False # ? output/storenewset
result_format: OutputFormat = Field(default=OutputFormat.TXT, exclude=True)

@field_validator("horizon", mode="before")
def transform_horizon_to_str(cls, val: t.Union[str, int, None]) -> t.Optional[str]:
# horizon can be an int.
return str(val) if val else val # type: ignore


@all_optional_model
class GeneralParameters(DefaultGeneralParameters):
pass


class GeneralParametersLocal(DefaultGeneralParameters):
@property
def ini_fields(self) -> dict:
return {
"general": {
"mode": str(self.mode).title(),
"horizon": self.horizon,
"nbyears": str(self.nb_years),
"simulation.start": str(self.first_day),
"simulation.end": str(self.last_day),
"january.1st": str(self.first_january).title(),
"first-month-in-year": str(self.first_month).title(),
"first.weekday": str(self.first_week_day).title(),
"leapyear": str(self.leap_year).lower(),
"year-by-year": str(self.year_by_year).lower(),
"derated": str(self.building_mode == BuildingMode.DERATED).lower(),
"custom-scenario": str(self.building_mode == BuildingMode.CUSTOM).lower(),
"user-playlist": str(self.selection_mode).lower(),
"thematic-trimming": str(self.thematic_trimming).lower(),
"geographic-trimming": str(self.geographic_trimming).lower(),
"readonly": str(self.read_only).lower(),
},
"input": {},
"output": {
"synthesis": str(self.simulation_synthesis).lower(),
"storenewset": str(self.mc_scenario).lower(),
"result-format": self.result_format.value,
},
}

def yield_properties(self) -> GeneralParameters:
return GeneralParameters.model_validate(self.model_dump(exclude_none=True))
@dataclass
class GeneralParameters:
mode: Optional[Mode] = None
horizon: Optional[str] = None
nb_years: Optional[int] = None
simulation_start: Optional[int] = None
simulation_end: Optional[int] = None
january_first: Optional[WeekDay] = None
first_month_in_year: Optional[Month] = None
first_week_day: Optional[WeekDay] = None
leap_year: Optional[bool] = None
year_by_year: Optional[bool] = None
simulation_synthesis: Optional[bool] = None
building_mode: Optional[BuildingMode] = None
user_playlist: Optional[bool] = None
thematic_trimming: Optional[bool] = None
geographic_trimming: Optional[bool] = None
store_new_set: Optional[bool] = None
nb_timeseries_thermal: Optional[int] = None
Loading

0 comments on commit a15b685

Please sign in to comment.