diff --git a/src/antares/craft/model/study.py b/src/antares/craft/model/study.py index c5fe7eaa..43ad1140 100644 --- a/src/antares/craft/model/study.py +++ b/src/antares/craft/model/study.py @@ -38,7 +38,7 @@ from antares.craft.service.api_services.study_api import _returns_study_settings from antares.craft.service.base_services import BaseStudyService from antares.craft.service.service_factory import ServiceFactory -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes """ The study module defines the data model for antares study. @@ -140,7 +140,7 @@ def create_study_local( desktop_ini_file.write(desktop_ini_content) local_settings = StudySettingsLocal.model_validate(settings) - local_settings_file = IniFile(study_directory, IniFileTypes.GENERAL) + local_settings_file = IniFile(study_directory, InitializationFilesTypes.GENERAL) local_settings_file.ini_dict = local_settings.model_dump(exclude_none=True, by_alias=True) local_settings_file.write_ini_file() @@ -174,7 +174,7 @@ def _directory_not_exists(local_path: Path) -> None: _directory_not_exists(study_directory) - study_antares = IniFile(study_directory, IniFileTypes.ANTARES) + study_antares = IniFile(study_directory, InitializationFilesTypes.ANTARES) study_params = study_antares.ini_dict["antares"] @@ -480,7 +480,7 @@ def _create_correlation_ini_files(local_settings: StudySettingsLocal, study_dire correlation_inis_to_create = [ ( field + "_correlation", - getattr(IniFileTypes, field.upper() + "_CORRELATION_INI"), + getattr(InitializationFilesTypes, field.upper() + "_CORRELATION_INI"), field, ) for field in fields_to_check diff --git a/src/antares/craft/service/local_services/area_local.py b/src/antares/craft/service/local_services/area_local.py index 730b1739..fa2ff1e7 100644 --- a/src/antares/craft/service/local_services/area_local.py +++ b/src/antares/craft/service/local_services/area_local.py @@ -39,7 +39,7 @@ BaseThermalService, ) from antares.craft.tools.contents_tool import transform_name_to_id -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import read_timeseries from antares.craft.tools.prepro_folder import PreproFolder from antares.craft.tools.time_series_tool import TimeSeriesFileType @@ -90,7 +90,10 @@ def create_thermal_cluster( args = {"thermal_name": thermal_name, **properties.model_dump(mode="json", exclude_none=True)} local_thermal_properties = ThermalClusterPropertiesLocal.model_validate(args) - list_ini = IniFile(self.config.study_path, IniFileTypes.THERMAL_LIST_INI, area_id=area_id) + list_ini = IniFile(self.config.study_path, InitializationFilesTypes.THERMAL_LIST_INI, area_id=area_id) + IniFile(self.config.study_path, InitializationFilesTypes.THERMAL_PREPRO_MODULATION, area_id=area_id) + IniFile(self.config.study_path, InitializationFilesTypes.THERMAL_PREPRO_DATA, area_id=area_id) + IniFile(self.config.study_path, InitializationFilesTypes.THERMAL_SERIES, area_id=area_id) try: list_ini.add_section(local_thermal_properties.list_ini_fields) except DuplicateSectionError: @@ -129,7 +132,7 @@ def create_renewable_cluster( args = {"renewable_name": renewable_name, **properties.model_dump(mode="json", exclude_none=True)} local_properties = RenewableClusterPropertiesLocal.model_validate(args) - list_ini = IniFile(self.config.study_path, IniFileTypes.RENEWABLES_LIST_INI, area_id=area_id) + list_ini = IniFile(self.config.study_path, InitializationFilesTypes.RENEWABLES_LIST_INI, area_id=area_id) list_ini.add_section(local_properties.ini_fields) list_ini.write_ini_file() @@ -152,7 +155,7 @@ def create_st_storage( args = {"st_storage_name": st_storage_name, **properties.model_dump(mode="json", exclude_none=True)} local_st_storage_properties = STStoragePropertiesLocal.model_validate(args) - list_ini = IniFile(self.config.study_path, IniFileTypes.ST_STORAGE_LIST_INI, area_id=area_id) + list_ini = IniFile(self.config.study_path, InitializationFilesTypes.ST_STORAGE_LIST_INI, area_id=area_id) list_ini.add_section(local_st_storage_properties.list_ini_fields) list_ini.write_ini_file(sort_sections=True) @@ -187,10 +190,12 @@ def create_hydro( args = {"area_id": area_id, **properties.model_dump(mode="json", exclude_none=True)} local_hydro_properties = HydroPropertiesLocal.model_validate(args) - list_ini = IniFile(self.config.study_path, IniFileTypes.HYDRO_INI) + list_ini = IniFile(self.config.study_path, InitializationFilesTypes.HYDRO_INI) list_ini.add_section(local_hydro_properties.hydro_ini_fields, append=True) list_ini.write_ini_file(sort_section_content=True) + IniFile.create_hydro_initialization_files_for_area(self.config.study_path, area_id) + return Hydro(self, area_id, local_hydro_properties.yield_hydro_properties()) def read_hydro( @@ -255,7 +260,7 @@ def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: # TODO: Handle districts in sets.ini later sets_ini_content = _sets_ini_content() - with (self.config.study_path / IniFileTypes.AREAS_SETS_INI.value).open("w") as sets_ini: + with (self.config.study_path / InitializationFilesTypes.AREAS_SETS_INI.value).open("w") as sets_ini: sets_ini_content.write(sets_ini) local_properties = ( @@ -264,7 +269,9 @@ def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: else AreaPropertiesLocal() ) - adequacy_patch_ini = IniFile(self.config.study_path, IniFileTypes.AREA_ADEQUACY_PATCH_INI, area_name) + adequacy_patch_ini = IniFile( + self.config.study_path, InitializationFilesTypes.AREA_ADEQUACY_PATCH_INI, area_name + ) adequacy_patch_ini.add_section(local_properties.adequacy_patch()) adequacy_patch_ini.write_ini_file() @@ -274,7 +281,7 @@ def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: with open(new_area_directory / "optimization.ini", "w") as optimization_ini_file: optimization_ini.write(optimization_ini_file) - areas_ini = IniFile(self.config.study_path, IniFileTypes.THERMAL_AREAS_INI) + areas_ini = IniFile(self.config.study_path, InitializationFilesTypes.THERMAL_AREAS_INI) if not areas_ini.ini_dict: areas_ini.add_section({"unserverdenergycost": {}}) areas_ini.add_section({"spilledenergycost": {}}) @@ -290,6 +297,14 @@ def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: with open(new_area_directory / "ui.ini", "w") as ui_ini_file: ui_ini.write(ui_ini_file) + empty_df = pd.DataFrame() + self.create_reserves(area_name, empty_df) + self.create_misc_gen(area_name, empty_df) + self.create_load(area_name, empty_df) + self.create_solar(area_name, empty_df) + self.create_wind(area_name, empty_df) + IniFile.create_link_ini_for_area(self.config.study_path, area_name) + except Exception as e: raise AreaCreationError(area_name, f"{e}") from e @@ -347,13 +362,15 @@ def read_areas(self) -> List[Area]: for element in areas_path.iterdir(): if element.is_dir(): optimization_dict = IniFile( - self.config.study_path, IniFileTypes.AREA_OPTIMIZATION_INI, area_id=element.name + self.config.study_path, InitializationFilesTypes.AREA_OPTIMIZATION_INI, area_id=element.name ).ini_dict area_adequacy_dict = IniFile( - self.config.study_path, IniFileTypes.AREA_ADEQUACY_PATCH_INI, area_id=element.name + self.config.study_path, InitializationFilesTypes.AREA_ADEQUACY_PATCH_INI, area_id=element.name + ).ini_dict + ui_dict = IniFile( + self.config.study_path, InitializationFilesTypes.AREA_UI_INI, area_id=element.name ).ini_dict - ui_dict = IniFile(self.config.study_path, IniFileTypes.AREA_UI_INI, area_id=element.name).ini_dict - thermal_area_dict = IniFile(self.config.study_path, IniFileTypes.THERMAL_AREAS_INI).ini_dict + thermal_area_dict = IniFile(self.config.study_path, InitializationFilesTypes.THERMAL_AREAS_INI).ini_dict area_properties = AreaPropertiesLocal( non_dispatch_power=optimization_dict["nodal optimization"].get("non-dispatchable-power"), dispatch_hydro_power=optimization_dict["nodal optimization"].get("dispatchable-hydro-power"), diff --git a/src/antares/craft/service/local_services/binding_constraint_local.py b/src/antares/craft/service/local_services/binding_constraint_local.py index e1ebef5e..941a270c 100644 --- a/src/antares/craft/service/local_services/binding_constraint_local.py +++ b/src/antares/craft/service/local_services/binding_constraint_local.py @@ -26,7 +26,7 @@ DefaultBindingConstraintProperties, ) from antares.craft.service.base_services import BaseBindingConstraintService -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import df_read, df_save from antares.craft.tools.time_series_tool import TimeSeriesFileType from pydantic import Field @@ -77,7 +77,7 @@ def __init__(self, config: LocalConfiguration, study_name: str, **kwargs: Any) - super().__init__(**kwargs) self.config = config self.study_name = study_name - self.ini_file = IniFile(self.config.study_path, IniFileTypes.BINDING_CONSTRAINTS_INI) + self.ini_file = IniFile(self.config.study_path, InitializationFilesTypes.BINDING_CONSTRAINTS_INI) def create_binding_constraint( self, diff --git a/src/antares/craft/service/local_services/link_local.py b/src/antares/craft/service/local_services/link_local.py index 0fefa7b3..2627e9c6 100644 --- a/src/antares/craft/service/local_services/link_local.py +++ b/src/antares/craft/service/local_services/link_local.py @@ -23,7 +23,7 @@ from antares.craft.service.base_services import BaseLinkService from antares.craft.tools.contents_tool import sort_ini_sections from antares.craft.tools.custom_raw_config_parser import CustomRawConfigParser -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import read_timeseries from antares.craft.tools.time_series_tool import TimeSeriesFileType @@ -175,7 +175,9 @@ def read_links(self) -> list[Link]: for element in link_path.iterdir(): area_from = element.name - links_dict = IniFile(self.config.study_path, IniFileTypes.LINK_PROPERTIES_INI, area_id=area_from).ini_dict + links_dict = IniFile( + self.config.study_path, InitializationFilesTypes.LINK_PROPERTIES_INI, area_id=area_from + ).ini_dict # If the properties.ini doesn't exist, we stop the reading process if links_dict: for area_to in links_dict: diff --git a/src/antares/craft/service/local_services/renewable_local.py b/src/antares/craft/service/local_services/renewable_local.py index 3f957ca9..c5e8b479 100644 --- a/src/antares/craft/service/local_services/renewable_local.py +++ b/src/antares/craft/service/local_services/renewable_local.py @@ -18,7 +18,7 @@ from antares.craft.config.local_configuration import LocalConfiguration from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties, RenewableClusterPropertiesLocal from antares.craft.service.base_services import BaseRenewableService -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import read_timeseries from antares.craft.tools.time_series_tool import TimeSeriesFileType @@ -40,7 +40,9 @@ def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: ) def read_renewables(self, area_id: str) -> List[RenewableCluster]: - renewable_dict = IniFile(self.config.study_path, IniFileTypes.RENEWABLES_LIST_INI, area_id=area_id).ini_dict + renewable_dict = IniFile( + self.config.study_path, InitializationFilesTypes.RENEWABLES_LIST_INI, area_id=area_id + ).ini_dict renewables_clusters = [] if renewable_dict: for renewable_cluster in renewable_dict: diff --git a/src/antares/craft/service/local_services/thermal_local.py b/src/antares/craft/service/local_services/thermal_local.py index 82788052..c8540585 100644 --- a/src/antares/craft/service/local_services/thermal_local.py +++ b/src/antares/craft/service/local_services/thermal_local.py @@ -22,7 +22,7 @@ ThermalClusterPropertiesLocal, ) from antares.craft.service.base_services import BaseThermalService -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import read_timeseries from antares.craft.tools.time_series_tool import TimeSeriesFileType @@ -58,7 +58,9 @@ def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalCl ) def read_thermal_clusters(self, area_id: str) -> List[ThermalCluster]: - thermal_dict = IniFile(self.config.study_path, IniFileTypes.THERMAL_LIST_INI, area_id=area_id).ini_dict + thermal_dict = IniFile( + self.config.study_path, InitializationFilesTypes.THERMAL_LIST_INI, area_id=area_id + ).ini_dict thermal_clusters = [] if thermal_dict: for thermal_cluster in thermal_dict: diff --git a/src/antares/craft/tools/ini_tool.py b/src/antares/craft/tools/ini_tool.py index 966923fc..abeecaf7 100644 --- a/src/antares/craft/tools/ini_tool.py +++ b/src/antares/craft/tools/ini_tool.py @@ -19,9 +19,10 @@ from pydantic import BaseModel -class IniFileTypes(Enum): +class InitializationFilesTypes(Enum): """ - The different ini files in an Antares project, files that are created for each area require using + The different initialization files (ini or txt) in an Antares project, + files that are created for each area require using format(area_id=) to get the complete path """ @@ -36,8 +37,18 @@ class IniFileTypes(Enum): AREA_UI_INI = "input/areas/{area_id}/ui.ini" AREA_ADEQUACY_PATCH_INI = "input/areas/{area_id}/adequacy_patch.ini" BINDING_CONSTRAINTS_INI = "input/bindingconstraints/bindingconstraints.ini" + HYDRO_CORRELATION_INI = "input/hydro/prepro/correlation.ini" HYDRO_INI = "input/hydro/hydro.ini" + HYDRO_CAPACITY_CM_TXT = "input/hydro/common/capacity/creditmodulations_{area_id}.txt" + HYDRO_CAPACITY_RE_TXT = "input/hydro/common/capacity/reservoir_{area_id}.txt" + HYDRO_CAPACITY_WV_TXT = "input/hydro/common/capacity/waterValues_{area_id}.txt" + HYDRO_CAPACITY_IP_TXT = "input/hydro/common/capacity/inflowPattern_{area_id}.txt" + HYDRO_SERIES_ROR_TXT = "input/hydro/series/{area_id}/ror.txt" + HYDRO_SERIES_MOD_TXT = "input/hydro/series/{area_id}/mod.txt" + HYDRO_SERIES_MIN_GEN_TXT = "input/hydro/series/{area_id}/mingen.txt" + HYDRO_COMMON_MAX_POWER = "input/hydro/common/capacity/maxpower_{area_id}.txt" + LINK_PROPERTIES_INI = "input/links/{area_id}/properties.ini" LOAD_CORRELATION_INI = "input/load/prepro/correlation.ini" LOAD_SETTINGS_INI = "input/load/prepro/{area_id}/settings.ini" @@ -45,17 +56,23 @@ class IniFileTypes(Enum): SOLAR_CORRELATION_INI = "input/solar/prepro/correlation.ini" SOLAR_SETTINGS_INI = "input/solar/prepro/{area_id}/settings.ini" ST_STORAGE_LIST_INI = "input/st-storage/clusters/{area_id}/list.ini" + THERMAL_AREAS_INI = "input/thermal/areas.ini" THERMAL_LIST_INI = "input/thermal/clusters/{area_id}/list.ini" + THERMAL_PREPRO_MODULATION = "input/thermal/prepro/{area_id}/cluster/modulation.txt" + THERMAL_PREPRO_DATA = "input/thermal/prepro/{area_id}/cluster/data.txt" + THERMAL_SERIES = "input/thermal/series/{area_id}/cluster/series.txt" + WIND_CORRELATION_INI = "input/wind/prepro/correlation.ini" WIND_SETTINGS_INI = "input/wind/prepro/{area_id}/settings.ini" + WIND_SERIES = "input/wind/series/wind_{area_id}.txt" class IniFile: def __init__( self, study_path: Path, - ini_file_type: IniFileTypes, + ini_file_type: InitializationFilesTypes, area_id: Optional[str] = None, ini_contents: Union[CustomRawConfigParser, dict[str, dict[str, str]], None] = None, ) -> None: @@ -171,6 +188,38 @@ def _sort_ini_section_content(ini_to_sort: CustomRawConfigParser) -> CustomRawCo sorted_ini[section] = {key: value for (key, value) in sorted(list(ini_to_sort[section].items()))} return sorted_ini + @classmethod + def create_hydro_initialization_files_for_area(cls, study_path: Path, area_id: str) -> None: + """ + Creates IniFile instances for HYDRO_CAPACITY files + + Args: + study_path (Path): The base path for the study. + area_id (str): The area ID. + + Returns: + list[IniFile]: A list of IniFile instances for the capacity files. + """ + capacity_files = [ + InitializationFilesTypes.HYDRO_CAPACITY_CM_TXT, + InitializationFilesTypes.HYDRO_CAPACITY_RE_TXT, + InitializationFilesTypes.HYDRO_CAPACITY_WV_TXT, + InitializationFilesTypes.HYDRO_CAPACITY_IP_TXT, + InitializationFilesTypes.HYDRO_SERIES_ROR_TXT, + InitializationFilesTypes.HYDRO_SERIES_MOD_TXT, + InitializationFilesTypes.HYDRO_SERIES_MIN_GEN_TXT, + InitializationFilesTypes.HYDRO_COMMON_MAX_POWER, + ] + + for file_type in capacity_files: + cls(study_path=study_path, ini_file_type=file_type, area_id=area_id) + + @classmethod + def create_link_ini_for_area(cls, study_path: Path, area_id: str) -> None: + property_file = InitializationFilesTypes.LINK_PROPERTIES_INI + + cls(study_path=study_path, ini_file_type=property_file, area_id=area_id) + def merge_dicts_for_ini(dict_a: dict[str, Any], dict_b: dict[str, Any]) -> dict: """ diff --git a/src/antares/craft/tools/prepro_folder.py b/src/antares/craft/tools/prepro_folder.py index 4bf20905..cfd1580e 100644 --- a/src/antares/craft/tools/prepro_folder.py +++ b/src/antares/craft/tools/prepro_folder.py @@ -15,7 +15,7 @@ import numpy as np import pandas as pd -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import df_save from antares.craft.tools.time_series_tool import TimeSeriesFileType @@ -26,7 +26,7 @@ class PreproFolder(Enum): WIND = "wind" def save(self, study_path: Path, area_id: str) -> None: - IniFile(study_path, IniFileTypes.__getitem__(f"{self.value.upper()}_SETTINGS_INI"), area_id) + IniFile(study_path, InitializationFilesTypes.__getitem__(f"{self.value.upper()}_SETTINGS_INI"), area_id) conversion = TimeSeriesFileType.__getitem__(f"{self.value.upper()}_CONVERSION").value.format(area_id=area_id) conversion_path = study_path.joinpath(conversion) diff --git a/tests/antares/integration/test_local_client.py b/tests/antares/integration/test_local_client.py index 84aed6c0..8087f3ce 100644 --- a/tests/antares/integration/test_local_client.py +++ b/tests/antares/integration/test_local_client.py @@ -26,7 +26,7 @@ from antares.craft.model.st_storage import STStorageGroup, STStorageProperties from antares.craft.model.study import Study, create_study_local from antares.craft.model.thermal import ThermalCluster, ThermalClusterGroup, ThermalClusterProperties -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes class TestLocalClient: @@ -118,7 +118,7 @@ def test_local_study(self, tmp_path, unknown_area): assert area_be.ui.x == area_ui.x assert area_be.ui.color_rgb == area_ui.color_rgb be_ui_file = test_study.service.config.study_path.joinpath( - IniFileTypes.AREA_UI_INI.value.format(area_id=area_be.id) + InitializationFilesTypes.AREA_UI_INI.value.format(area_id=area_be.id) ) assert be_ui_file.is_file() @@ -148,10 +148,12 @@ def test_local_study(self, tmp_path, unknown_area): assert link_be_fr.properties.hurdles_cost assert link_be_fr.properties.filter_year_by_year == {FilterOption.HOURLY} be_link_ini_file = test_study.service.config.study_path.joinpath( - IniFileTypes.LINK_PROPERTIES_INI.value.format(area_id=area_be.id) + InitializationFilesTypes.LINK_PROPERTIES_INI.value.format(area_id=area_be.id) ) assert be_link_ini_file.is_file() - be_links_in_file = IniFile(test_study.service.config.study_path, IniFileTypes.LINK_PROPERTIES_INI, area_be.id) + be_links_in_file = IniFile( + test_study.service.config.study_path, InitializationFilesTypes.LINK_PROPERTIES_INI, area_be.id + ) assert be_links_in_file.ini_dict["fr"]["hurdles-cost"] == "true" assert be_links_in_file.ini_dict["fr"]["filter-year-by-year"] == "hourly" diff --git a/tests/antares/services/local_services/conftest.py b/tests/antares/services/local_services/conftest.py index e56aebad..bf20aec6 100644 --- a/tests/antares/services/local_services/conftest.py +++ b/tests/antares/services/local_services/conftest.py @@ -31,7 +31,7 @@ ThermalClusterProperties, ThermalCostGeneration, ) -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes @pytest.fixture @@ -111,17 +111,21 @@ def default_thermal_cluster_properties() -> ThermalClusterProperties: @pytest.fixture def actual_thermal_list_ini(local_study_w_thermal) -> IniFile: - return IniFile(local_study_w_thermal.service.config.study_path, IniFileTypes.THERMAL_LIST_INI, area_id="fr") + return IniFile( + local_study_w_thermal.service.config.study_path, InitializationFilesTypes.THERMAL_LIST_INI, area_id="fr" + ) @pytest.fixture def actual_thermal_areas_ini(local_study_w_thermal) -> IniFile: - return IniFile(local_study_w_thermal.service.config.study_path, IniFileTypes.THERMAL_AREAS_INI) + return IniFile(local_study_w_thermal.service.config.study_path, InitializationFilesTypes.THERMAL_AREAS_INI) @pytest.fixture def actual_adequacy_patch_ini(local_study_w_areas) -> IniFile: - return IniFile(local_study_w_areas.service.config.study_path, IniFileTypes.AREA_ADEQUACY_PATCH_INI, area_id="fr") + return IniFile( + local_study_w_areas.service.config.study_path, InitializationFilesTypes.AREA_ADEQUACY_PATCH_INI, area_id="fr" + ) @pytest.fixture @@ -146,7 +150,9 @@ def default_renewable_cluster_properties() -> RenewableClusterProperties: @pytest.fixture def actual_renewable_list_ini(local_study_with_renewable) -> IniFile: - return IniFile(local_study_with_renewable.service.config.study_path, IniFileTypes.RENEWABLES_LIST_INI, area_id="fr") + return IniFile( + local_study_with_renewable.service.config.study_path, InitializationFilesTypes.RENEWABLES_LIST_INI, area_id="fr" + ) @pytest.fixture @@ -173,7 +179,9 @@ def default_st_storage_properties() -> STStorageProperties: @pytest.fixture def actual_st_storage_list_ini(local_study_with_st_storage) -> IniFile: return IniFile( - local_study_with_st_storage.service.config.study_path, IniFileTypes.ST_STORAGE_LIST_INI, area_id="fr" + local_study_with_st_storage.service.config.study_path, + InitializationFilesTypes.ST_STORAGE_LIST_INI, + area_id="fr", ) @@ -206,7 +214,7 @@ def default_hydro_properties() -> HydroProperties: @pytest.fixture def actual_hydro_ini(local_study_with_hydro) -> IniFile: - return IniFile(local_study_with_hydro.service.config.study_path, IniFileTypes.HYDRO_INI) + return IniFile(local_study_with_hydro.service.config.study_path, InitializationFilesTypes.HYDRO_INI) @pytest.fixture diff --git a/tests/antares/services/local_services/test_area.py b/tests/antares/services/local_services/test_area.py index 7ce5bc8c..a6abe474 100644 --- a/tests/antares/services/local_services/test_area.py +++ b/tests/antares/services/local_services/test_area.py @@ -23,8 +23,6 @@ import pandas as pd from antares.craft.config.local_configuration import LocalConfiguration -from antares.craft.exceptions.exceptions import ThermalCreationError -from antares.craft.model.hydro import Hydro from antares.craft.model.renewable import ( RenewableCluster, RenewableClusterGroup, @@ -34,265 +32,12 @@ ) from antares.craft.model.st_storage import STStorage, STStorageGroup, STStorageProperties, STStoragePropertiesLocal from antares.craft.model.study import read_study_local -from antares.craft.model.thermal import ( - LawOption, - LocalTSGenerationBehavior, - ThermalCluster, - ThermalClusterGroup, - ThermalClusterProperties, - ThermalClusterPropertiesLocal, - ThermalCostGeneration, -) -from antares.craft.tools.ini_tool import IniFile, IniFileTypes +from antares.craft.model.thermal import ThermalCluster +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes from antares.craft.tools.matrix_tool import df_save from antares.craft.tools.time_series_tool import TimeSeriesFileType -class TestCreateThermalCluster: - def test_can_be_created(self, local_study_w_areas): - # Given - thermal_name = "test_thermal_cluster" - - # When - created_thermal = local_study_w_areas.get_areas()["fr"].create_thermal_cluster(thermal_name) - assert isinstance(created_thermal, ThermalCluster) - - def test_duplicate_name_errors(self, local_study_w_thermal): - # Given - area_name = "fr" - thermal_name = "test thermal cluster" - - # Then - with pytest.raises( - ThermalCreationError, - match=f"Could not create the thermal cluster {thermal_name} inside area {area_name}: A thermal cluster called '{thermal_name}' already exists in area '{area_name}'.", - ): - local_study_w_thermal.get_areas()[area_name].create_thermal_cluster(thermal_name) - - def test_has_default_properties(self, local_study_w_thermal): - assert ( - local_study_w_thermal.get_areas()["fr"] - .get_thermals()["test thermal cluster"] - .properties.model_dump(exclude_none=True) - ) - - def test_has_correct_default_properties(self, local_study_w_thermal, default_thermal_cluster_properties): - # Given - expected_thermal_cluster_properties = default_thermal_cluster_properties - - # When - actual_thermal_cluster_properties = ( - local_study_w_thermal.get_areas()["fr"].get_thermals()["test thermal cluster"].properties - ) - - assert expected_thermal_cluster_properties == actual_thermal_cluster_properties - - def test_required_ini_files_exist(self, tmp_path, local_study_w_thermal): - # Given - expected_list_ini_path = ( - local_study_w_thermal.service.config.study_path / IniFileTypes.THERMAL_LIST_INI.value.format(area_id="fr") - ) - expected_areas_ini_path = local_study_w_thermal.service.config.study_path / IniFileTypes.THERMAL_AREAS_INI.value - - # Then - assert expected_list_ini_path.is_file() - assert expected_areas_ini_path.is_file() - - def test_list_ini_has_default_properties(self, tmp_path, local_study_w_thermal, actual_thermal_list_ini): - # Given - expected_list_ini_contents = """[test thermal cluster] -group = Other 1 -name = test thermal cluster -enabled = True -unitcount = 1 -nominalcapacity = 0.000000 -gen-ts = use global -min-stable-power = 0.000000 -min-up-time = 1 -min-down-time = 1 -must-run = False -spinning = 0.000000 -volatility.forced = 0.000000 -volatility.planned = 0.000000 -law.forced = uniform -law.planned = uniform -marginal-cost = 0.000000 -spread-cost = 0.000000 -fixed-cost = 0.000000 -startup-cost = 0.000000 -market-bid-cost = 0.000000 -co2 = 0.000000 -nh3 = 0.000000 -so2 = 0.000000 -nox = 0.000000 -pm2_5 = 0.000000 -pm5 = 0.000000 -pm10 = 0.000000 -nmvoc = 0.000000 -op1 = 0.000000 -op2 = 0.000000 -op3 = 0.000000 -op4 = 0.000000 -op5 = 0.000000 -costgeneration = SetManually -efficiency = 100.000000 -variableomcost = 0.000000 - -""" - expected_list_ini = ConfigParser() - expected_list_ini.read_string(expected_list_ini_contents) - with actual_thermal_list_ini.ini_path.open("r") as actual_list_ini_file: - actual_list_ini_contents = actual_list_ini_file.read() - - # Then - assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() - assert actual_list_ini_contents == expected_list_ini_contents - assert actual_thermal_list_ini.parsed_ini == expected_list_ini - - def test_list_ini_has_custom_properties(self, tmp_path, local_study_w_areas): - # Given - expected_list_ini_contents = """[test thermal cluster] -group = Nuclear -name = test thermal cluster -enabled = False -unitcount = 12 -nominalcapacity = 3.900000 -gen-ts = force no generation -min-stable-power = 3.100000 -min-up-time = 3 -min-down-time = 2 -must-run = True -spinning = 2.300000 -volatility.forced = 3.500000 -volatility.planned = 3.700000 -law.forced = geometric -law.planned = geometric -marginal-cost = 2.900000 -spread-cost = 4.200000 -fixed-cost = 3.600000 -startup-cost = 0.700000 -market-bid-cost = 0.800000 -co2 = 1.000000 -nh3 = 2.000000 -so2 = 3.000000 -nox = 4.000000 -pm2_5 = 5.000000 -pm5 = 6.000000 -pm10 = 7.000000 -nmvoc = 8.000000 -op1 = 9.000000 -op2 = 10.000000 -op3 = 11.000000 -op4 = 12.000000 -op5 = 13.000000 -costgeneration = useCostTimeseries -efficiency = 123.400000 -variableomcost = 5.000000 - -""" - expected_list_ini = ConfigParser() - expected_list_ini.read_string(expected_list_ini_contents) - thermal_cluster_properties = ThermalClusterProperties( - group=ThermalClusterGroup.NUCLEAR, - enabled=False, - unit_count=12, - nominal_capacity=3.9, - gen_ts=LocalTSGenerationBehavior.FORCE_NO_GENERATION, - min_stable_power=3.1, - min_up_time=3, - min_down_time=2, - must_run=True, - spinning=2.3, - volatility_forced=3.5, - volatility_planned=3.7, - law_forced=LawOption.GEOMETRIC, - law_planned=LawOption.GEOMETRIC, - marginal_cost=2.9, - spread_cost=4.2, - fixed_cost=3.6, - startup_cost=0.7, - market_bid_cost=0.8, - co2=1.0, - nh3=2.0, - so2=3.0, - nox=4.0, - pm2_5=5.0, - pm5=6.0, - pm10=7.0, - nmvoc=8.0, - op1=9.0, - op2=10.0, - op3=11.0, - op4=12.0, - op5=13.0, - cost_generation=ThermalCostGeneration.USE_COST_TIME_SERIES, - efficiency=123.4, - variable_o_m_cost=5.0, - ) - - # When - local_study_w_areas.get_areas()["fr"].create_thermal_cluster("test thermal cluster", thermal_cluster_properties) - actual_thermal_list_ini = IniFile( - local_study_w_areas.service.config.study_path, IniFileTypes.THERMAL_LIST_INI, area_id="fr" - ) - actual_thermal_list_ini.update_from_ini_file() - with actual_thermal_list_ini.ini_path.open("r") as actual_list_ini_file: - actual_list_ini_contents = actual_list_ini_file.read() - - # Then - assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() - assert actual_list_ini_contents == expected_list_ini_contents - assert actual_thermal_list_ini.parsed_ini == expected_list_ini - - def test_list_ini_has_multiple_clusters( - self, local_study_w_thermal, actual_thermal_list_ini, default_thermal_cluster_properties - ): - # Given - local_study_w_thermal.get_areas()["fr"].create_thermal_cluster("test thermal cluster two") - args = default_thermal_cluster_properties.model_dump(mode="json", exclude_none=True) - args["thermal_name"] = "test thermal cluster" - expected_list_ini_dict = ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields - args["thermal_name"] = "test thermal cluster two" - expected_list_ini_dict.update(ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields) - - expected_list_ini = ConfigParser() - expected_list_ini.read_dict(expected_list_ini_dict) - - # When - actual_thermal_list_ini.update_from_ini_file() - - # Then - assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() - assert actual_thermal_list_ini.parsed_ini == expected_list_ini - - def test_clusters_are_alphabetical_in_list_ini( - self, local_study_w_thermal, actual_thermal_list_ini, default_thermal_cluster_properties - ): - # Given - first_cluster_alphabetically = "a is before b and t" - second_cluster_alphabetically = "b is after a" - - args = default_thermal_cluster_properties.model_dump(mode="json", exclude_none=True) - args["thermal_name"] = first_cluster_alphabetically - expected_list_ini_dict = ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields - args["thermal_name"] = second_cluster_alphabetically - expected_list_ini_dict.update(ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields) - args["thermal_name"] = "test thermal cluster" - expected_list_ini_dict.update(ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields) - expected_list_ini = ConfigParser() - expected_list_ini.read_dict(expected_list_ini_dict) - - # When - local_study_w_thermal.get_areas()["fr"].create_thermal_cluster(second_cluster_alphabetically) - local_study_w_thermal.get_areas()["fr"].create_thermal_cluster(first_cluster_alphabetically) - actual_thermal_list_ini.update_from_ini_file() - - # Then - assert actual_thermal_list_ini.ini_dict.keys() == expected_list_ini_dict.keys() - assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() - assert actual_thermal_list_ini.parsed_ini == expected_list_ini - - class TestCreateRenewablesCluster: def test_can_create_renewables_cluster(self, local_study_w_thermal): # When @@ -325,7 +70,7 @@ def test_renewable_cluster_has_correct_default_properties( def test_renewables_list_ini_exists(self, local_study_with_renewable): renewables_list_ini = ( local_study_with_renewable.service.config.study_path - / IniFileTypes.RENEWABLES_LIST_INI.value.format(area_id="fr") + / InitializationFilesTypes.RENEWABLES_LIST_INI.value.format(area_id="fr") ) assert renewables_list_ini.is_file() @@ -370,7 +115,7 @@ def test_renewable_cluster_and_ini_have_custom_properties(self, local_study_w_th """ actual_renewable_list_ini = IniFile( - local_study_w_thermal.service.config.study_path, IniFileTypes.RENEWABLES_LIST_INI, area_id="fr" + local_study_w_thermal.service.config.study_path, InitializationFilesTypes.RENEWABLES_LIST_INI, area_id="fr" ) # When @@ -415,7 +160,7 @@ def test_storage_has_correct_default_properties(self, local_study_with_st_storag def test_st_storage_list_ini_exists(self, local_study_with_st_storage): st_storage_list_ini = ( local_study_with_st_storage.service.config.study_path - / IniFileTypes.ST_STORAGE_LIST_INI.value.format(area_id="fr") + / InitializationFilesTypes.ST_STORAGE_LIST_INI.value.format(area_id="fr") ) assert st_storage_list_ini.is_file() @@ -464,7 +209,7 @@ def test_st_storage_and_ini_have_custom_properties(self, local_study_w_areas): """ actual_st_storage_list_ini = IniFile( - local_study_w_areas.service.config.study_path, IniFileTypes.ST_STORAGE_LIST_INI, area_id="fr" + local_study_w_areas.service.config.study_path, InitializationFilesTypes.ST_STORAGE_LIST_INI, area_id="fr" ) # When @@ -482,190 +227,6 @@ def test_st_storage_and_ini_have_custom_properties(self, local_study_w_areas): assert actual_st_storage_list_ini_content == expected_st_storage_list_ini_content -class TestCreateHydro: - def test_can_create_hydro(self, local_study_with_st_storage): - # When - local_study_with_st_storage.get_areas()["fr"].create_hydro() - - # Then - assert local_study_with_st_storage.get_areas()["fr"].hydro - assert isinstance(local_study_with_st_storage.get_areas()["fr"].hydro, Hydro) - - def test_hydro_has_properties(self, local_study_w_areas): - assert local_study_w_areas.get_areas()["fr"].hydro.properties - - def test_hydro_has_correct_default_properties(self, local_study_w_areas, default_hydro_properties): - assert local_study_w_areas.get_areas()["fr"].hydro.properties == default_hydro_properties - - def test_hydro_ini_exists(self, local_study_w_areas): - hydro_ini = local_study_w_areas.service.config.study_path / IniFileTypes.HYDRO_INI.value - assert hydro_ini.is_file() - - def test_hydro_ini_has_correct_default_values(self, local_study_w_areas): - # Given - expected_hydro_ini_content = """[inter-daily-breakdown] -fr = 1.000000 -it = 1.000000 - -[intra-daily-modulation] -fr = 24.000000 -it = 24.000000 - -[inter-monthly-breakdown] -fr = 1.000000 -it = 1.000000 - -[reservoir] -fr = false -it = false - -[reservoir capacity] -fr = 0.000000 -it = 0.000000 - -[follow load] -fr = true -it = true - -[use water] -fr = false -it = false - -[hard bounds] -fr = false -it = false - -[initialize reservoir date] -fr = 0 -it = 0 - -[use heuristic] -fr = true -it = true - -[power to level] -fr = false -it = false - -[use leeway] -fr = false -it = false - -[leeway low] -fr = 1.000000 -it = 1.000000 - -[leeway up] -fr = 1.000000 -it = 1.000000 - -[pumping efficiency] -fr = 1.000000 -it = 1.000000 - -""" - expected_hydro_ini = ConfigParser() - expected_hydro_ini.read_string(expected_hydro_ini_content) - actual_hydro_ini = IniFile(local_study_w_areas.service.config.study_path, IniFileTypes.HYDRO_INI) - - # When - with actual_hydro_ini.ini_path.open() as st_storage_list_ini_file: - actual_hydro_ini_content = st_storage_list_ini_file.read() - - assert actual_hydro_ini_content == expected_hydro_ini_content - assert actual_hydro_ini.parsed_ini.sections() == expected_hydro_ini.sections() - assert actual_hydro_ini.parsed_ini == expected_hydro_ini - - def test_hydro_ini_has_correct_sorted_areas(self, actual_hydro_ini): - # Given - expected_hydro_ini_content = """[inter-daily-breakdown] -at = 1.000000 -fr = 1.000000 -it = 1.000000 - -[intra-daily-modulation] -at = 24.000000 -fr = 24.000000 -it = 24.000000 - -[inter-monthly-breakdown] -at = 1.000000 -fr = 1.000000 -it = 1.000000 - -[reservoir] -at = false -fr = false -it = false - -[reservoir capacity] -at = 0.000000 -fr = 0.000000 -it = 0.000000 - -[follow load] -at = true -fr = true -it = true - -[use water] -at = false -fr = false -it = false - -[hard bounds] -at = false -fr = false -it = false - -[initialize reservoir date] -at = 0 -fr = 0 -it = 0 - -[use heuristic] -at = true -fr = true -it = true - -[power to level] -at = false -fr = false -it = false - -[use leeway] -at = false -fr = false -it = false - -[leeway low] -at = 1.000000 -fr = 1.000000 -it = 1.000000 - -[leeway up] -at = 1.000000 -fr = 1.000000 -it = 1.000000 - -[pumping efficiency] -at = 1.000000 -fr = 1.000000 -it = 1.000000 - -""" - expected_hydro_ini = ConfigParser() - expected_hydro_ini.read_string(expected_hydro_ini_content) - - # When - with actual_hydro_ini.ini_path.open() as st_storage_list_ini_file: - actual_hydro_ini_content = st_storage_list_ini_file.read() - - assert actual_hydro_ini_content == expected_hydro_ini_content - assert actual_hydro_ini.parsed_ini.sections() == expected_hydro_ini.sections() - assert actual_hydro_ini.parsed_ini == expected_hydro_ini - - class TestCreateReserves: def test_can_create_reserves_ts_file(self, area_fr): # Given diff --git a/tests/antares/services/local_services/test_create_thermal_cluster.py b/tests/antares/services/local_services/test_create_thermal_cluster.py new file mode 100644 index 00000000..5477c466 --- /dev/null +++ b/tests/antares/services/local_services/test_create_thermal_cluster.py @@ -0,0 +1,293 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import pytest + +from configparser import ConfigParser +from pathlib import Path + +from antares.craft.exceptions.exceptions import ThermalCreationError +from antares.craft.model.thermal import ( + LawOption, + LocalTSGenerationBehavior, + ThermalCluster, + ThermalClusterGroup, + ThermalClusterProperties, + ThermalClusterPropertiesLocal, + ThermalCostGeneration, +) +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes + + +class TestCreateThermalCluster: + def test_can_be_created(self, local_study_w_areas): + # Given + thermal_name = "test_thermal_cluster" + + # When + created_thermal = local_study_w_areas.get_areas()["fr"].create_thermal_cluster(thermal_name) + assert isinstance(created_thermal, ThermalCluster) + + def test_duplicate_name_errors(self, local_study_w_thermal): + # Given + area_name = "fr" + thermal_name = "test thermal cluster" + + # Then + with pytest.raises( + ThermalCreationError, + match=f"Could not create the thermal cluster {thermal_name} inside area {area_name}: A thermal cluster called '{thermal_name}' already exists in area '{area_name}'.", + ): + local_study_w_thermal.get_areas()[area_name].create_thermal_cluster(thermal_name) + + def test_has_default_properties(self, local_study_w_thermal): + assert ( + local_study_w_thermal.get_areas()["fr"] + .get_thermals()["test thermal cluster"] + .properties.model_dump(exclude_none=True) + ) + + def test_has_correct_default_properties(self, local_study_w_thermal, default_thermal_cluster_properties): + # Given + expected_thermal_cluster_properties = default_thermal_cluster_properties + + # When + actual_thermal_cluster_properties = ( + local_study_w_thermal.get_areas()["fr"].get_thermals()["test thermal cluster"].properties + ) + + assert expected_thermal_cluster_properties == actual_thermal_cluster_properties + + def test_required_ini_files_exist(self, tmp_path, local_study_w_thermal): + # Given + expected_list_ini_path = ( + local_study_w_thermal.service.config.study_path + / InitializationFilesTypes.THERMAL_LIST_INI.value.format(area_id="fr") + ) + expected_areas_ini_path = ( + local_study_w_thermal.service.config.study_path / InitializationFilesTypes.THERMAL_AREAS_INI.value + ) + + # Then + assert expected_list_ini_path.is_file() + assert expected_areas_ini_path.is_file() + + def test_list_ini_has_default_properties(self, tmp_path, local_study_w_thermal, actual_thermal_list_ini): + # Given + expected_list_ini_contents = """[test thermal cluster] +group = Other 1 +name = test thermal cluster +enabled = True +unitcount = 1 +nominalcapacity = 0.000000 +gen-ts = use global +min-stable-power = 0.000000 +min-up-time = 1 +min-down-time = 1 +must-run = False +spinning = 0.000000 +volatility.forced = 0.000000 +volatility.planned = 0.000000 +law.forced = uniform +law.planned = uniform +marginal-cost = 0.000000 +spread-cost = 0.000000 +fixed-cost = 0.000000 +startup-cost = 0.000000 +market-bid-cost = 0.000000 +co2 = 0.000000 +nh3 = 0.000000 +so2 = 0.000000 +nox = 0.000000 +pm2_5 = 0.000000 +pm5 = 0.000000 +pm10 = 0.000000 +nmvoc = 0.000000 +op1 = 0.000000 +op2 = 0.000000 +op3 = 0.000000 +op4 = 0.000000 +op5 = 0.000000 +costgeneration = SetManually +efficiency = 100.000000 +variableomcost = 0.000000 + +""" + expected_list_ini = ConfigParser() + expected_list_ini.read_string(expected_list_ini_contents) + with actual_thermal_list_ini.ini_path.open("r") as actual_list_ini_file: + actual_list_ini_contents = actual_list_ini_file.read() + + # Then + assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() + assert actual_list_ini_contents == expected_list_ini_contents + assert actual_thermal_list_ini.parsed_ini == expected_list_ini + + def test_list_ini_has_custom_properties(self, tmp_path, local_study_w_areas): + # Given + expected_list_ini_contents = """[test thermal cluster] +group = Nuclear +name = test thermal cluster +enabled = False +unitcount = 12 +nominalcapacity = 3.900000 +gen-ts = force no generation +min-stable-power = 3.100000 +min-up-time = 3 +min-down-time = 2 +must-run = True +spinning = 2.300000 +volatility.forced = 3.500000 +volatility.planned = 3.700000 +law.forced = geometric +law.planned = geometric +marginal-cost = 2.900000 +spread-cost = 4.200000 +fixed-cost = 3.600000 +startup-cost = 0.700000 +market-bid-cost = 0.800000 +co2 = 1.000000 +nh3 = 2.000000 +so2 = 3.000000 +nox = 4.000000 +pm2_5 = 5.000000 +pm5 = 6.000000 +pm10 = 7.000000 +nmvoc = 8.000000 +op1 = 9.000000 +op2 = 10.000000 +op3 = 11.000000 +op4 = 12.000000 +op5 = 13.000000 +costgeneration = useCostTimeseries +efficiency = 123.400000 +variableomcost = 5.000000 + +""" + expected_list_ini = ConfigParser() + expected_list_ini.read_string(expected_list_ini_contents) + thermal_cluster_properties = ThermalClusterProperties( + group=ThermalClusterGroup.NUCLEAR, + enabled=False, + unit_count=12, + nominal_capacity=3.9, + gen_ts=LocalTSGenerationBehavior.FORCE_NO_GENERATION, + min_stable_power=3.1, + min_up_time=3, + min_down_time=2, + must_run=True, + spinning=2.3, + volatility_forced=3.5, + volatility_planned=3.7, + law_forced=LawOption.GEOMETRIC, + law_planned=LawOption.GEOMETRIC, + marginal_cost=2.9, + spread_cost=4.2, + fixed_cost=3.6, + startup_cost=0.7, + market_bid_cost=0.8, + co2=1.0, + nh3=2.0, + so2=3.0, + nox=4.0, + pm2_5=5.0, + pm5=6.0, + pm10=7.0, + nmvoc=8.0, + op1=9.0, + op2=10.0, + op3=11.0, + op4=12.0, + op5=13.0, + cost_generation=ThermalCostGeneration.USE_COST_TIME_SERIES, + efficiency=123.4, + variable_o_m_cost=5.0, + ) + + # When + local_study_w_areas.get_areas()["fr"].create_thermal_cluster("test thermal cluster", thermal_cluster_properties) + actual_thermal_list_ini = IniFile( + local_study_w_areas.service.config.study_path, InitializationFilesTypes.THERMAL_LIST_INI, area_id="fr" + ) + actual_thermal_list_ini.update_from_ini_file() + with actual_thermal_list_ini.ini_path.open("r") as actual_list_ini_file: + actual_list_ini_contents = actual_list_ini_file.read() + + # Then + assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() + assert actual_list_ini_contents == expected_list_ini_contents + assert actual_thermal_list_ini.parsed_ini == expected_list_ini + + def test_list_ini_has_multiple_clusters( + self, local_study_w_thermal, actual_thermal_list_ini, default_thermal_cluster_properties + ): + # Given + local_study_w_thermal.get_areas()["fr"].create_thermal_cluster("test thermal cluster two") + args = default_thermal_cluster_properties.model_dump(mode="json", exclude_none=True) + args["thermal_name"] = "test thermal cluster" + expected_list_ini_dict = ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields + args["thermal_name"] = "test thermal cluster two" + expected_list_ini_dict.update(ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields) + + expected_list_ini = ConfigParser() + expected_list_ini.read_dict(expected_list_ini_dict) + + # When + actual_thermal_list_ini.update_from_ini_file() + + # Then + assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() + assert actual_thermal_list_ini.parsed_ini == expected_list_ini + + def test_clusters_are_alphabetical_in_list_ini( + self, local_study_w_thermal, actual_thermal_list_ini, default_thermal_cluster_properties + ): + # Given + first_cluster_alphabetically = "a is before b and t" + second_cluster_alphabetically = "b is after a" + + args = default_thermal_cluster_properties.model_dump(mode="json", exclude_none=True) + args["thermal_name"] = first_cluster_alphabetically + expected_list_ini_dict = ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields + args["thermal_name"] = second_cluster_alphabetically + expected_list_ini_dict.update(ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields) + args["thermal_name"] = "test thermal cluster" + expected_list_ini_dict.update(ThermalClusterPropertiesLocal.model_validate(args).list_ini_fields) + expected_list_ini = ConfigParser() + expected_list_ini.read_dict(expected_list_ini_dict) + + # When + local_study_w_thermal.get_areas()["fr"].create_thermal_cluster(second_cluster_alphabetically) + local_study_w_thermal.get_areas()["fr"].create_thermal_cluster(first_cluster_alphabetically) + actual_thermal_list_ini.update_from_ini_file() + + # Then + assert actual_thermal_list_ini.ini_dict.keys() == expected_list_ini_dict.keys() + assert actual_thermal_list_ini.parsed_ini.sections() == expected_list_ini.sections() + assert actual_thermal_list_ini.parsed_ini == expected_list_ini + + def test_create_thermal_initialization_files(self, local_study_w_areas): + study_path = Path(local_study_w_areas.path) + areas = local_study_w_areas.get_areas() + + for area_id, area in areas.items(): + area.create_thermal_cluster("cluster_test") + + for area_id in areas.keys(): + expected_paths = [ + study_path / f"input/thermal/prepro/{area_id}/cluster/modulation.txt", + study_path / f"input/thermal/prepro/{area_id}/cluster/data.txt", + study_path / f"input/thermal/series/{area_id}/cluster/series.txt", + ] + + for expected_path in expected_paths: + assert expected_path.is_file(), f"File not created: {expected_path}" diff --git a/tests/antares/services/local_services/test_hydro.py b/tests/antares/services/local_services/test_hydro.py new file mode 100644 index 00000000..f5aa68bf --- /dev/null +++ b/tests/antares/services/local_services/test_hydro.py @@ -0,0 +1,226 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +from configparser import ConfigParser +from pathlib import Path + +from antares.craft.model.hydro import Hydro +from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes + + +class TestCreateHydro: + def test_can_create_hydro(self, local_study_with_st_storage): + # When + local_study_with_st_storage.get_areas()["fr"].create_hydro() + + # Then + assert local_study_with_st_storage.get_areas()["fr"].hydro + assert isinstance(local_study_with_st_storage.get_areas()["fr"].hydro, Hydro) + + def test_hydro_has_properties(self, local_study_w_areas): + assert local_study_w_areas.get_areas()["fr"].hydro.properties + + def test_hydro_has_correct_default_properties(self, local_study_w_areas, default_hydro_properties): + assert local_study_w_areas.get_areas()["fr"].hydro.properties == default_hydro_properties + + def test_hydro_ini_exists(self, local_study_w_areas): + hydro_ini = local_study_w_areas.service.config.study_path / InitializationFilesTypes.HYDRO_INI.value + assert hydro_ini.is_file() + + def test_create_capacity_files_for_hydro(self, local_study_w_areas): + """ + Test that calling create_hydro on all areas triggers the creation of capacity files. + """ + study_path = Path(local_study_w_areas.path) + areas = local_study_w_areas.get_areas() + + for area_id, area in areas.items(): + area.create_hydro() + + for area_id in areas.keys(): + expected_paths = [ + study_path / f"input/hydro/common/capacity/creditmodulations_{area_id}.txt", + study_path / f"input/hydro/common/capacity/reservoir_{area_id}.txt", + study_path / f"input/hydro/common/capacity/waterValues_{area_id}.txt", + study_path / f"input/hydro/common/capacity/inflowPattern_{area_id}.txt", + study_path / f"input/hydro/series/{area_id}/ror.txt", + study_path / f"input/hydro/series/{area_id}/mod.txt", + study_path / f"input/hydro/series/{area_id}/mingen.txt", + study_path / f"input/hydro/common/capacity/maxpower_{area_id}.txt", + ] + + for expected_path in expected_paths: + assert expected_path.is_file(), f"File not created: {expected_path}" + + def test_hydro_ini_has_correct_default_values(self, local_study_w_areas): + # Given + expected_hydro_ini_content = """[inter-daily-breakdown] +fr = 1.000000 +it = 1.000000 + +[intra-daily-modulation] +fr = 24.000000 +it = 24.000000 + +[inter-monthly-breakdown] +fr = 1.000000 +it = 1.000000 + +[reservoir] +fr = false +it = false + +[reservoir capacity] +fr = 0.000000 +it = 0.000000 + +[follow load] +fr = true +it = true + +[use water] +fr = false +it = false + +[hard bounds] +fr = false +it = false + +[initialize reservoir date] +fr = 0 +it = 0 + +[use heuristic] +fr = true +it = true + +[power to level] +fr = false +it = false + +[use leeway] +fr = false +it = false + +[leeway low] +fr = 1.000000 +it = 1.000000 + +[leeway up] +fr = 1.000000 +it = 1.000000 + +[pumping efficiency] +fr = 1.000000 +it = 1.000000 + +""" + expected_hydro_ini = ConfigParser() + expected_hydro_ini.read_string(expected_hydro_ini_content) + actual_hydro_ini = IniFile(local_study_w_areas.service.config.study_path, InitializationFilesTypes.HYDRO_INI) + + # When + with actual_hydro_ini.ini_path.open() as st_storage_list_ini_file: + actual_hydro_ini_content = st_storage_list_ini_file.read() + + assert actual_hydro_ini_content == expected_hydro_ini_content + assert actual_hydro_ini.parsed_ini.sections() == expected_hydro_ini.sections() + assert actual_hydro_ini.parsed_ini == expected_hydro_ini + + def test_hydro_ini_has_correct_sorted_areas(self, actual_hydro_ini): + # Given + expected_hydro_ini_content = """[inter-daily-breakdown] +at = 1.000000 +fr = 1.000000 +it = 1.000000 + +[intra-daily-modulation] +at = 24.000000 +fr = 24.000000 +it = 24.000000 + +[inter-monthly-breakdown] +at = 1.000000 +fr = 1.000000 +it = 1.000000 + +[reservoir] +at = false +fr = false +it = false + +[reservoir capacity] +at = 0.000000 +fr = 0.000000 +it = 0.000000 + +[follow load] +at = true +fr = true +it = true + +[use water] +at = false +fr = false +it = false + +[hard bounds] +at = false +fr = false +it = false + +[initialize reservoir date] +at = 0 +fr = 0 +it = 0 + +[use heuristic] +at = true +fr = true +it = true + +[power to level] +at = false +fr = false +it = false + +[use leeway] +at = false +fr = false +it = false + +[leeway low] +at = 1.000000 +fr = 1.000000 +it = 1.000000 + +[leeway up] +at = 1.000000 +fr = 1.000000 +it = 1.000000 + +[pumping efficiency] +at = 1.000000 +fr = 1.000000 +it = 1.000000 + +""" + expected_hydro_ini = ConfigParser() + expected_hydro_ini.read_string(expected_hydro_ini_content) + + # When + with actual_hydro_ini.ini_path.open() as st_storage_list_ini_file: + actual_hydro_ini_content = st_storage_list_ini_file.read() + + assert actual_hydro_ini_content == expected_hydro_ini_content + assert actual_hydro_ini.parsed_ini.sections() == expected_hydro_ini.sections() + assert actual_hydro_ini.parsed_ini == expected_hydro_ini diff --git a/tests/antares/services/local_services/test_study.py b/tests/antares/services/local_services/test_study.py index 14fb4f88..fee5f5b1 100644 --- a/tests/antares/services/local_services/test_study.py +++ b/tests/antares/services/local_services/test_study.py @@ -85,7 +85,7 @@ ThematicTrimmingParametersLocal, ) from antares.craft.model.study import create_study_local -from antares.craft.tools.ini_tool import IniFileTypes +from antares.craft.tools.ini_tool import InitializationFilesTypes class TestCreateStudy: @@ -608,7 +608,7 @@ def test_generaldata_ini_has_correct_default_values(self, local_study): """ # When - actual_generaldata_ini_file = local_study.service.config.study_path / IniFileTypes.GENERAL.value + actual_generaldata_ini_file = local_study.service.config.study_path / InitializationFilesTypes.GENERAL.value actual_file_content = actual_generaldata_ini_file.read_text() # Then @@ -730,7 +730,7 @@ def test_generaldata_ini_with_thematic_trimming_has_negative_sign(self, tmp_path general_parameters=general_parameters, thematic_trimming_parameters=thematic_trimming_parameters ), ) - actual_generaldata_ini_file = new_study.service.config.study_path / IniFileTypes.GENERAL.value + actual_generaldata_ini_file = new_study.service.config.study_path / InitializationFilesTypes.GENERAL.value actual_file_content = actual_generaldata_ini_file.read_text() # Then @@ -855,7 +855,7 @@ def test_generaldata_ini_with_thematic_trimming_has_positive_sign(self, tmp_path general_parameters=general_parameters, thematic_trimming_parameters=thematic_trimming_parameters ), ) - actual_generaldata_ini_file = new_study.service.config.study_path / IniFileTypes.GENERAL.value + actual_generaldata_ini_file = new_study.service.config.study_path / InitializationFilesTypes.GENERAL.value actual_file_content = actual_generaldata_ini_file.read_text() # Then @@ -983,7 +983,7 @@ def test_generaldata_ini_with_thematic_trimming_two_variables(self, tmp_path): ), ) - actual_generaldata_ini_file_path = new_study.service.config.study_path / IniFileTypes.GENERAL.value + actual_generaldata_ini_file_path = new_study.service.config.study_path / InitializationFilesTypes.GENERAL.value actual_file_content = actual_generaldata_ini_file_path.read_text() # Then @@ -1116,7 +1116,9 @@ def test_generaldata_ini_with_playlist_has_negative_sign(self, tmp_path): ), ) - actual_general_parameters_file_path = new_study.service.config.study_path / IniFileTypes.GENERAL.value + actual_general_parameters_file_path = ( + new_study.service.config.study_path / InitializationFilesTypes.GENERAL.value + ) actual_file_content = actual_general_parameters_file_path.read_text() # Then @@ -1251,7 +1253,7 @@ def test_generaldata_ini_with_playlist_has_positive_sign(self, tmp_path): ), ) - actual_general_ini_file_path = new_study.service.config.study_path / IniFileTypes.GENERAL.value + actual_general_ini_file_path = new_study.service.config.study_path / InitializationFilesTypes.GENERAL.value actual_file_content = actual_general_ini_file_path.read_text() # Then @@ -1259,6 +1261,23 @@ def test_generaldata_ini_with_playlist_has_positive_sign(self, tmp_path): class TestCreateArea: + def test_initialization_when_creating_area(self, tmp_path, local_study): + study_path = Path(local_study.path) + area_id = "test_files" + local_study.create_area(area_id) + + expected_paths = [ + study_path / f"input/reserves/{area_id}.txt", + study_path / f"input/misc-gen/miscgen-{area_id}.txt", + study_path / f"input/links/{area_id}/properties.ini", + study_path / f"input/load/series/load_{area_id}.txt", + study_path / f"input/solar/series/solar_{area_id}.txt", + study_path / f"input/wind/series/wind_{area_id}.txt", + ] + + for expected_path in expected_paths: + assert expected_path.is_file(), f"File not created: {expected_path}" + def test_areas_sets_ini_content(self, tmp_path, local_study): # Given expected_sets_path = tmp_path / local_study.name / "input" / "areas" / "sets.ini" @@ -2066,7 +2085,8 @@ def test_constraints_ini_have_correct_default_content( # When actual_ini_path = ( - local_study_with_constraint.service.config.study_path / IniFileTypes.BINDING_CONSTRAINTS_INI.value + local_study_with_constraint.service.config.study_path + / InitializationFilesTypes.BINDING_CONSTRAINTS_INI.value ) with actual_ini_path.open("r") as file: actual_ini_content = file.read() @@ -2114,7 +2134,8 @@ def test_constraints_and_ini_have_custom_properties(self, local_study_with_const name="test constraint two", properties=custom_constraint_properties ) actual_file_path = ( - local_study_with_constraint.service.config.study_path / IniFileTypes.BINDING_CONSTRAINTS_INI.value + local_study_with_constraint.service.config.study_path + / InitializationFilesTypes.BINDING_CONSTRAINTS_INI.value ) with actual_file_path.open("r") as file: actual_ini_content = file.read()