diff --git a/src/andromede/input_converter/src/converter.py b/src/andromede/input_converter/src/converter.py index bee899a2..7f171f0e 100644 --- a/src/andromede/input_converter/src/converter.py +++ b/src/andromede/input_converter/src/converter.py @@ -258,6 +258,115 @@ def _convert_thermal_to_component_list( ) return components, connections + def _convert_st_storage_to_component_list( + self, areas: Iterable[Area], lib_id: str + ) -> tuple[list[InputComponent], list[InputPortConnections]]: + components = [] + connections = [] + self.logger.info("Converting short-term storages to component list...") + # Add thermal components for each area + for area in areas: + storages = area.get_st_storages() + for storage in storages.values(): + series_path = ( + self.study_path + / "input" + / "st-storage" + / "series" + / Path(storage.area_id) + / Path(storage.id) + ) + inflows_path = series_path / "inflows" + lower_rule_curve_path = series_path / "lower-rule-curve" + pmax_injection_path = series_path / "PMAX-injection" + pmax_withdrawal_path = series_path / "PMAX-withdrawal" + upper_rule_curve_path = series_path / "upper-rule-curve" + components.append( + InputComponent( + id=storage.id, + model=f"{lib_id}.short-term-storage", + parameters=[ + InputComponentParameter( + id="efficiency_injection", + time_dependent=False, + scenario_dependent=False, + value=storage.properties.efficiency, + ), + # TODO wait for update of antares craft that support the 9.2 version of Antares + # InputComponentParameter( + # id="efficiency_withdrawal", + # time_dependent=False, + # scenario_dependent=False, + # value=storage.properties.efficiencywithdrawal, + # ), + InputComponentParameter( + id="initial_level", + time_dependent=False, + scenario_dependent=True, + value=storage.properties.initial_level, + ), + InputComponentParameter( + id="reservoir_capacity", + time_dependent=False, + scenario_dependent=False, + value=storage.properties.reservoir_capacity, + ), + InputComponentParameter( + id="injection_nominal_capacity", + time_dependent=False, + scenario_dependent=False, + value=storage.properties.injection_nominal_capacity, + ), + InputComponentParameter( + id="withdrawal_nominal_capacity", + time_dependent=False, + scenario_dependent=False, + value=storage.properties.withdrawal_nominal_capacity, + ), + InputComponentParameter( + id="inflows", + time_dependent=True, + scenario_dependent=True, + value=str(inflows_path), + ), + InputComponentParameter( + id="lower_rule_curve", + time_dependent=True, + scenario_dependent=True, + value=str(lower_rule_curve_path), + ), + InputComponentParameter( + id="upper_rule_curve", + time_dependent=True, + scenario_dependent=True, + value=str(upper_rule_curve_path), + ), + InputComponentParameter( + id="p_max_injection_modulation", + time_dependent=True, + scenario_dependent=True, + value=str(pmax_injection_path), + ), + InputComponentParameter( + id="p_max_withdrawal_modulation", + time_dependent=True, + scenario_dependent=True, + value=str(pmax_withdrawal_path), + ), + ], + ) + ) + + connections.append( + InputPortConnections( + component1=storage.id, + port1="injection_port", + component2=area.id, + port2="balance_port", + ) + ) + return components, connections + def _convert_link_to_component_list( self, lib_id: str ) -> tuple[list[InputComponent], list[InputPortConnections]]: @@ -451,6 +560,7 @@ def convert_study_to_input_study(self) -> InputSystem: conversion_methods = [ self._convert_renewable_to_component_list, self._convert_thermal_to_component_list, + self._convert_st_storage_to_component_list, self._convert_load_to_component_list, self._convert_wind_to_component_list, self._convert_solar_to_component_list, diff --git a/tests/input_converter/conftest.py b/tests/input_converter/conftest.py index 99af9665..dae392a0 100644 --- a/tests/input_converter/conftest.py +++ b/tests/input_converter/conftest.py @@ -14,6 +14,7 @@ from antares.craft.model.area import Area, AreaProperties from antares.craft.model.hydro import HydroProperties from antares.craft.model.renewable import RenewableClusterProperties +from antares.craft.model.st_storage import STStorageProperties from antares.craft.model.study import Study, create_study_local from antares.craft.model.thermal import ThermalClusterProperties from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes @@ -139,7 +140,22 @@ def actual_renewable_list_ini(local_study_with_renewable) -> IniFile: @pytest.fixture -def local_study_with_st_storage(local_study_with_renewable) -> Study: +def default_storage_cluster_properties() -> STStorageProperties: + return STStorageProperties( + injection_nominal_capacity=10, + withdrawal_nominal_capacity=10, + reservoir_capacity=0, + efficiency=1, + initial_level=0.5, + initial_level_optim=False, + enabled=True, + ) + + +@pytest.fixture +def local_study_with_st_storage( + local_study_with_renewable, default_storage_cluster_properties +) -> Study: """ Create an empty study Create 2 areas with custom area properties @@ -148,8 +164,10 @@ def local_study_with_st_storage(local_study_with_renewable) -> Study: Create a renewable cluster Create a short term storage """ - storage_name = "short term storage" - local_study_with_renewable.get_areas()["fr"].create_st_storage(storage_name) + storage_name = "battery" + local_study_with_renewable.get_areas()["fr"].create_st_storage( + storage_name, properties=default_storage_cluster_properties + ) return local_study_with_renewable diff --git a/tests/input_converter/test_converter.py b/tests/input_converter/test_converter.py index d057fd69..3f342ad4 100644 --- a/tests/input_converter/test_converter.py +++ b/tests/input_converter/test_converter.py @@ -215,6 +215,113 @@ def test_convert_renewables_to_component( assert renewables_components == expected_renewable_component assert renewable_connections == expected_renewable_connections + def test_convert_st_storages_to_component( + self, local_study_with_st_storage, lib_id: str + ): + areas, converter = self._init_area_reading(local_study_with_st_storage) + study_path = converter.study_path + ( + storage_components, + storage_connections, + ) = converter._convert_st_storage_to_component_list(areas, lib_id) + default_path = study_path / "input" / "st-storage" / "series" / "fr" / "battery" + inflows_path = default_path / "inflows" + lower_rule_curve_path = default_path / "lower-rule-curve" + pmax_injection_path = default_path / "PMAX-injection" + pmax_withdrawal_path = default_path / "PMAX-withdrawal" + upper_rule_curve_path = default_path / "upper-rule-curve" + expected_storage_connections = [ + InputPortConnections( + component1="battery", + port1="injection_port", + component2="fr", + port2="balance_port", + ) + ] + expected_storage_component = [ + InputComponent( + id="battery", + model=f"{lib_id}.short-term-storage", + scenario_group=None, + parameters=[ + InputComponentParameter( + id="efficiency_injection", + time_dependent=False, + scenario_dependent=False, + scenario_group=None, + value=1, + ), + InputComponentParameter( + id="initial_level", + time_dependent=False, + scenario_dependent=True, + scenario_group=None, + value=0.5, + ), + InputComponentParameter( + id="reservoir_capacity", + time_dependent=False, + scenario_dependent=False, + scenario_group=None, + value=0.0, + ), + InputComponentParameter( + id="injection_nominal_capacity", + time_dependent=False, + scenario_dependent=False, + scenario_group=None, + value=10.0, + ), + InputComponentParameter( + id="withdrawal_nominal_capacity", + time_dependent=False, + scenario_dependent=False, + scenario_group=None, + value=10.0, + ), + InputComponentParameter( + id="inflows", + time_dependent=True, + scenario_dependent=True, + scenario_group=None, + value=f"{inflows_path}", + ), + InputComponentParameter( + id="lower_rule_curve", + time_dependent=True, + scenario_dependent=True, + scenario_group=None, + value=f"{lower_rule_curve_path}", + ), + InputComponentParameter( + id="upper_rule_curve", + time_dependent=True, + scenario_dependent=True, + scenario_group=None, + value=f"{upper_rule_curve_path}", + ), + InputComponentParameter( + id="p_max_injection_modulation", + time_dependent=True, + scenario_dependent=True, + scenario_group=None, + value=f"{pmax_injection_path}", + ), + InputComponentParameter( + id="p_max_withdrawal_modulation", + time_dependent=True, + scenario_dependent=True, + scenario_group=None, + value=f"{pmax_withdrawal_path}", + ), + ], + ) + ] + print("actual: ", storage_components) + print("epxected: ", expected_storage_component) + assert storage_components == expected_storage_component + assert storage_connections == expected_storage_connections + def test_convert_thermals_to_component( self, local_study_w_thermal: Study,