From e8aa128b1f5aa3e72594817b92d27d87f453dae5 Mon Sep 17 00:00:00 2001 From: killian-scalian Date: Thu, 10 Apr 2025 10:42:15 +0200 Subject: [PATCH 1/6] Add st storage to input converter w tests --- .../input_converter/src/converter.py | 110 +++++++++++++ tests/input_converter/conftest.py | 24 ++- tests/input_converter/test_converter.py | 146 ++++++++++++++++++ 3 files changed, 277 insertions(+), 3 deletions(-) diff --git a/src/andromede/input_converter/src/converter.py b/src/andromede/input_converter/src/converter.py index bee899a2..d2f0888f 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="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), + ), + InputComponentParameter( + id="upper_rule_curve", + time_dependent=True, + scenario_dependent=True, + value=str(upper_rule_curve_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..d62f98ef 100644 --- a/tests/input_converter/conftest.py +++ b/tests/input_converter/conftest.py @@ -13,6 +13,7 @@ import pytest from antares.craft.model.area import Area, AreaProperties from antares.craft.model.hydro import HydroProperties +from antares.craft.model.st_storage import STStorageProperties from antares.craft.model.renewable import RenewableClusterProperties from antares.craft.model.study import Study, create_study_local from antares.craft.model.thermal import ThermalClusterProperties @@ -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_thermal_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_thermal_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_thermal_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..bbf84df3 100644 --- a/tests/input_converter/test_converter.py +++ b/tests/input_converter/test_converter.py @@ -215,6 +215,152 @@ 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) + + inflows_path = ( + study_path + / "input" + / "st-storage" + / "series" + / "fr" + / "battery" + / "inflows" + ) + lower_rule_curve_path = ( + study_path + / "input" + / "st-storage" + / "series" + / "fr" + / "battery" + / "lower-rule-curve" + ) + pmax_injection_path = ( + study_path + / "input" + / "st-storage" + / "series" + / "fr" + / "battery" + / "PMAX-injection" + ) + pmax_withdrawal_path = ( + study_path + / "input" + / "st-storage" + / "series" + / "fr" + / "battery" + / "PMAX-withdrawal" + ) + upper_rule_curve_path = ( + study_path + / "input" + / "st-storage" + / "series" + / "fr" + / "battery" + / "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="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}", + ), + InputComponentParameter( + id="upper_rule_curve", + time_dependent=True, + scenario_dependent=True, + scenario_group=None, + value=f"{upper_rule_curve_path}", + ), + ], + ) + ] + + assert storage_components == expected_storage_component + assert storage_connections == expected_storage_connections + def test_convert_thermals_to_component( self, local_study_w_thermal: Study, From 75074ec7e1d5e6c41f0ece51ed873c77873c8400 Mon Sep 17 00:00:00 2001 From: killian-scalian Date: Thu, 10 Apr 2025 10:44:44 +0200 Subject: [PATCH 2/6] isort --- tests/input_converter/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/input_converter/conftest.py b/tests/input_converter/conftest.py index d62f98ef..b6975327 100644 --- a/tests/input_converter/conftest.py +++ b/tests/input_converter/conftest.py @@ -13,8 +13,8 @@ import pytest from antares.craft.model.area import Area, AreaProperties from antares.craft.model.hydro import HydroProperties -from antares.craft.model.st_storage import STStorageProperties 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 From e0763fb815dbb86ba808b8a5db23ead06ba6c5a3 Mon Sep 17 00:00:00 2001 From: killian-scalian Date: Thu, 10 Apr 2025 14:14:24 +0200 Subject: [PATCH 3/6] visibility improvement --- .../input_converter/src/converter.py | 12 ++-- src/andromede/input_converter/src/main.py | 4 +- tests/input_converter/conftest.py | 6 +- tests/input_converter/test_converter.py | 67 ++++--------------- 4 files changed, 25 insertions(+), 64 deletions(-) diff --git a/src/andromede/input_converter/src/converter.py b/src/andromede/input_converter/src/converter.py index d2f0888f..7f171f0e 100644 --- a/src/andromede/input_converter/src/converter.py +++ b/src/andromede/input_converter/src/converter.py @@ -336,22 +336,22 @@ def _convert_st_storage_to_component_list( value=str(lower_rule_curve_path), ), InputComponentParameter( - id="p_max_injection_modulation", + id="upper_rule_curve", time_dependent=True, scenario_dependent=True, - value=str(pmax_injection_path), + value=str(upper_rule_curve_path), ), InputComponentParameter( - id="p_max_withdrawal_modulation", + id="p_max_injection_modulation", time_dependent=True, scenario_dependent=True, - value=str(pmax_withdrawal_path), + value=str(pmax_injection_path), ), InputComponentParameter( - id="upper_rule_curve", + id="p_max_withdrawal_modulation", time_dependent=True, scenario_dependent=True, - value=str(upper_rule_curve_path), + value=str(pmax_withdrawal_path), ), ], ) diff --git a/src/andromede/input_converter/src/main.py b/src/andromede/input_converter/src/main.py index cb4aff77..4ab572bb 100644 --- a/src/andromede/input_converter/src/main.py +++ b/src/andromede/input_converter/src/main.py @@ -16,8 +16,8 @@ from configparser import ConfigParser from pathlib import Path -from .converter import AntaresStudyConverter -from .logger import Logger +from converter import AntaresStudyConverter +from logger import Logger DEFAULT: dict = {} LOGGER_PATH: str = os.path.join(os.path.dirname(__file__), "../data/logging.log") diff --git a/tests/input_converter/conftest.py b/tests/input_converter/conftest.py index b6975327..dae392a0 100644 --- a/tests/input_converter/conftest.py +++ b/tests/input_converter/conftest.py @@ -140,7 +140,7 @@ def actual_renewable_list_ini(local_study_with_renewable) -> IniFile: @pytest.fixture -def default_thermal_cluster_properties() -> STStorageProperties: +def default_storage_cluster_properties() -> STStorageProperties: return STStorageProperties( injection_nominal_capacity=10, withdrawal_nominal_capacity=10, @@ -154,7 +154,7 @@ def default_thermal_cluster_properties() -> STStorageProperties: @pytest.fixture def local_study_with_st_storage( - local_study_with_renewable, default_thermal_cluster_properties + local_study_with_renewable, default_storage_cluster_properties ) -> Study: """ Create an empty study @@ -166,7 +166,7 @@ def local_study_with_st_storage( """ storage_name = "battery" local_study_with_renewable.get_areas()["fr"].create_st_storage( - storage_name, properties=default_thermal_cluster_properties + 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 bbf84df3..3f342ad4 100644 --- a/tests/input_converter/test_converter.py +++ b/tests/input_converter/test_converter.py @@ -224,52 +224,12 @@ def test_convert_st_storages_to_component( storage_components, storage_connections, ) = converter._convert_st_storage_to_component_list(areas, lib_id) - - inflows_path = ( - study_path - / "input" - / "st-storage" - / "series" - / "fr" - / "battery" - / "inflows" - ) - lower_rule_curve_path = ( - study_path - / "input" - / "st-storage" - / "series" - / "fr" - / "battery" - / "lower-rule-curve" - ) - pmax_injection_path = ( - study_path - / "input" - / "st-storage" - / "series" - / "fr" - / "battery" - / "PMAX-injection" - ) - pmax_withdrawal_path = ( - study_path - / "input" - / "st-storage" - / "series" - / "fr" - / "battery" - / "PMAX-withdrawal" - ) - upper_rule_curve_path = ( - study_path - / "input" - / "st-storage" - / "series" - / "fr" - / "battery" - / "upper-rule-curve" - ) + 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", @@ -334,30 +294,31 @@ def test_convert_st_storages_to_component( value=f"{lower_rule_curve_path}", ), InputComponentParameter( - id="p_max_injection_modulation", + id="upper_rule_curve", time_dependent=True, scenario_dependent=True, scenario_group=None, - value=f"{pmax_injection_path}", + value=f"{upper_rule_curve_path}", ), InputComponentParameter( - id="p_max_withdrawal_modulation", + id="p_max_injection_modulation", time_dependent=True, scenario_dependent=True, scenario_group=None, - value=f"{pmax_withdrawal_path}", + value=f"{pmax_injection_path}", ), InputComponentParameter( - id="upper_rule_curve", + id="p_max_withdrawal_modulation", time_dependent=True, scenario_dependent=True, scenario_group=None, - value=f"{upper_rule_curve_path}", + 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 From 647c3f484d60d35b1e536decc4610132d11520d4 Mon Sep 17 00:00:00 2001 From: killian-scalian Date: Thu, 10 Apr 2025 14:19:17 +0200 Subject: [PATCH 4/6] solve issue with import --- src/andromede/input_converter/src/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/andromede/input_converter/src/main.py b/src/andromede/input_converter/src/main.py index 4ab572bb..b08c3856 100644 --- a/src/andromede/input_converter/src/main.py +++ b/src/andromede/input_converter/src/main.py @@ -15,7 +15,7 @@ from argparse import ArgumentParser, ArgumentTypeError, Namespace from configparser import ConfigParser from pathlib import Path - +sys.path.append(os.path.dirname(__file__)) from converter import AntaresStudyConverter from logger import Logger From 49578dac9706c70c752878fcf7e0e3f4d2093fd2 Mon Sep 17 00:00:00 2001 From: killian-scalian Date: Thu, 10 Apr 2025 14:20:15 +0200 Subject: [PATCH 5/6] sort --- src/andromede/input_converter/src/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/andromede/input_converter/src/main.py b/src/andromede/input_converter/src/main.py index b08c3856..55b5f9e3 100644 --- a/src/andromede/input_converter/src/main.py +++ b/src/andromede/input_converter/src/main.py @@ -15,6 +15,7 @@ from argparse import ArgumentParser, ArgumentTypeError, Namespace from configparser import ConfigParser from pathlib import Path + sys.path.append(os.path.dirname(__file__)) from converter import AntaresStudyConverter from logger import Logger From 7de4f2dad9323ce0ca965e6307fe971a64144b49 Mon Sep 17 00:00:00 2001 From: killian-scalian Date: Thu, 10 Apr 2025 14:25:10 +0200 Subject: [PATCH 6/6] back to normal --- src/andromede/input_converter/src/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/andromede/input_converter/src/main.py b/src/andromede/input_converter/src/main.py index 55b5f9e3..cb4aff77 100644 --- a/src/andromede/input_converter/src/main.py +++ b/src/andromede/input_converter/src/main.py @@ -16,9 +16,8 @@ from configparser import ConfigParser from pathlib import Path -sys.path.append(os.path.dirname(__file__)) -from converter import AntaresStudyConverter -from logger import Logger +from .converter import AntaresStudyConverter +from .logger import Logger DEFAULT: dict = {} LOGGER_PATH: str = os.path.join(os.path.dirname(__file__), "../data/logging.log")