diff --git a/src/andromede/input_converter/src/converter.py b/src/andromede/input_converter/src/converter.py index ffc0aec2..5f235a04 100644 --- a/src/andromede/input_converter/src/converter.py +++ b/src/andromede/input_converter/src/converter.py @@ -16,7 +16,13 @@ from antares.craft.model.study import Study, read_study_local from andromede.input_converter.src.utils import resolve_path -from andromede.study.parsing import InputComponent, InputComponentParameter, InputStudy +from andromede.study.parsing import ( + InputComponent, + InputComponentParameter, + InputStudy, + InputPortConnections, +) +from pandas import DataFrame class AntaresStudyConverter: @@ -33,6 +39,19 @@ def __init__(self, study_input: Union[Path, Study]): else: raise TypeError("Invalid input type") + def _check_dataframe_validity(self, df: DataFrame) -> bool: + """ + Check and validate the following conditions: + 1. The dataframe from this path is not empty. + 2. The dataframe does not contains only zero values. + + :param df: dataframe to validate. + """ + if df.empty or (df == 0).all().all(): + return False + + return True + def _convert_area_to_component_list( self, areas: list[Area] ) -> list[InputComponent]: @@ -60,8 +79,9 @@ def _convert_area_to_component_list( def _convert_renewable_to_component_list( self, areas: list[Area] - ) -> list[InputComponent]: + ) -> tuple[list[InputComponent], list[InputPortConnections]]: components = [] + connections = [] for area in areas: renewables = area.read_renewables() for renewable in renewables: @@ -97,13 +117,22 @@ def _convert_renewable_to_component_list( ], ) ) + connections.append( + InputPortConnections( + component1=renewable.id, + port_1="balance_port", + component2=area.id, + port_2="balance_port", + ) + ) - return components + return components, connections def _convert_thermal_to_component_list( self, areas: list[Area] - ) -> list[InputComponent]: + ) -> tuple[list[InputComponent], list[InputPortConnections]]: components = [] + connections = [] # Add thermal components for each area for area in areas: thermals = area.read_thermal_clusters() @@ -160,20 +189,147 @@ def _convert_thermal_to_component_list( ], ) ) - return components + connections.append( + InputPortConnections( + component1=thermal.id, + port_1="balance_port", + component2=area.id, + port_2="balance_port", + ) + ) + return components, connections + + def _convert_wind_to_component_list( + self, areas: list[Area] + ) -> tuple[list[InputComponent], list[InputPortConnections]]: + components = [] + connections = [] + for area in areas: + series_path = ( + self.study_path / "input" / "wind" / "series" / f"wind_{area.id}.txt" + ) + if series_path.exists(): + if self._check_dataframe_validity(area.get_wind_matrix()): + components.append( + InputComponent( + id=area.id, + model="wind", + parameters=[ + InputComponentParameter( + name="wind", + type="timeseries", + timeseries=str(series_path), + ) + ], + ) + ) + connections.append( + InputPortConnections( + component1="wind", + port_1="balance_port", + component2=area.id, + port_2="balance_port", + ) + ) + + return components, connections + + def _convert_solar_to_component_list( + self, areas: list[Area] + ) -> tuple[list[InputComponent], list[InputPortConnections]]: + components = [] + connections = [] + for area in areas: + series_path = ( + self.study_path / "input" / "solar" / "series" / f"solar_{area.id}.txt" + ) + + if series_path.exists(): + if self._check_dataframe_validity(area.get_solar_matrix()): + components.append( + InputComponent( + id=area.id, + model="solar", + parameters=[ + InputComponentParameter( + name="solar", + type="timeseries", + timeseries=str(series_path), + ) + ], + ) + ) + connections.append( + InputPortConnections( + component1="solar", + port_1="balance_port", + component2=area.id, + port_2="balance_port", + ) + ) + + return components, connections + + def _convert_load_to_component_list( + self, areas: list[Area] + ) -> tuple[list[InputComponent], list[InputPortConnections]]: + components = [] + connections = [] + for area in areas: + series_path = ( + self.study_path / "input" / "load" / "series" / f"load_{area.id}.txt" + ) + if series_path.exists(): + if self._check_dataframe_validity(area.get_load_matrix()): + components.append( + InputComponent( + id=area.id, + model="load", + parameters=[ + InputComponentParameter( + name="load", + type="timeseries", + timeseries=str(series_path), + ) + ], + ) + ) + connections.append( + InputPortConnections( + component1="load", + port_1="balance_port", + component2=area.id, + port_2="balance_port", + ) + ) + + return components, connections def convert_study_to_input_study(self) -> InputStudy: areas = self.study.read_areas() area_components = self._convert_area_to_component_list(areas) - list_components = [] - list_components.extend(self._convert_renewable_to_component_list(areas)) - list_components.extend(self._convert_thermal_to_component_list(areas)) - # loads = convert_load_matrix_to_component_list(areas, root_path) - - # winds = convert_wind_matrix_to_component_list(areas, root_path) - # solars = convert_solar_matrix_to_component_list(areas, root_path) - # misc_gens = convert_misc_gen_to_component_list(areas, root_path) - return InputStudy(nodes=area_components, components=list_components) + + list_components: list[InputComponent] = [] + list_connections: list[InputPortConnections] = [] + + conversion_methods = [ + self._convert_renewable_to_component_list, + self._convert_thermal_to_component_list, + self._convert_load_to_component_list, + self._convert_wind_to_component_list, + self._convert_solar_to_component_list, + ] + + for method in conversion_methods: + components, connections = method(areas) + list_components.extend(components) + list_connections.extend(connections) + + return InputStudy( + nodes=area_components, + components=list_components, + connections=list_connections, + ) def process_all(self) -> None: raise NotImplementedError diff --git a/tests/input_converter/conftest.py b/tests/input_converter/conftest.py index 337fe6d8..4f5179c4 100644 --- a/tests/input_converter/conftest.py +++ b/tests/input_converter/conftest.py @@ -245,17 +245,19 @@ def area_fr(local_study_with_hydro) -> Area: @pytest.fixture def fr_solar(area_fr) -> None: - return area_fr.create_solar(pd.DataFrame()) + return area_fr.create_solar(pd.DataFrame([1, 1, 1])) @pytest.fixture -def fr_wind(area_fr) -> None: - return area_fr.create_wind(pd.DataFrame()) +def fr_wind(area_fr, request) -> None: + command = request.param if hasattr(request, "param") else [1, 1, 1] + data = pd.DataFrame(command) + return area_fr.create_wind(data) @pytest.fixture def fr_load(area_fr) -> None: - return area_fr.create_load(pd.DataFrame()) + return area_fr.create_load(pd.DataFrame([1, 1, 1])) @pytest.fixture diff --git a/tests/input_converter/test_converter.py b/tests/input_converter/test_converter.py index 2509c590..8cf7669d 100644 --- a/tests/input_converter/test_converter.py +++ b/tests/input_converter/test_converter.py @@ -9,12 +9,14 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +import pytest from andromede.input_converter.src.converter import AntaresStudyConverter from andromede.input_converter.src.utils import transform_to_yaml from andromede.study.parsing import ( InputComponent, InputComponentParameter, + InputPortConnections, InputStudy, parse_yaml_components, ) @@ -161,7 +163,14 @@ def test_convert_study_to_input_study(self, local_study_w_thermal): ], ) ], - connections=[], + connections=[ + InputPortConnections( + component1="gaz", + port_1="balance_port", + component2="fr", + port_2="balance_port", + ) + ], ) # To ensure that the comparison between the actual and expected results is not affected by the order of the nodes, @@ -169,7 +178,6 @@ def test_convert_study_to_input_study(self, local_study_w_thermal): # This sorting step ensures that the test checks only the presence and validity of the nodes, not their order. input_study.nodes.sort(key=lambda x: x.id) expected_input_study.nodes.sort(key=lambda x: x.id) - assert input_study == expected_input_study def test_convert_area_to_component(self, local_study_w_areas): @@ -226,10 +234,13 @@ def test_convert_area_to_component(self, local_study_w_areas): area_components.sort(key=lambda x: x.id) assert area_components == expected_area_components - def test_convert_renewables_to_input_study(self, local_study_with_renewable): + def test_convert_renewables_to_component(self, local_study_with_renewable): areas, converter = self._init_area_reading(local_study_with_renewable) study_path = converter.study_path - renewables_components = converter._convert_renewable_to_component_list(areas) + ( + renewables_components, + renewable_connections, + ) = converter._convert_renewable_to_component_list(areas) timeserie_path = str( study_path @@ -240,6 +251,14 @@ def test_convert_renewables_to_input_study(self, local_study_with_renewable): / "generation" / "series.txt" ) + expected_renewable_connections = [ + InputPortConnections( + component1="generation", + port_1="balance_port", + component2="fr", + port_2="balance_port", + ) + ] expected_renewable_component = [ InputComponent( id="generation", @@ -271,16 +290,28 @@ def test_convert_renewables_to_input_study(self, local_study_with_renewable): ) ] assert renewables_components == expected_renewable_component + assert renewable_connections == expected_renewable_connections - def test_convert_thermals_to_input_study(self, local_study_w_thermal): + def test_convert_thermals_to_component(self, local_study_w_thermal): areas, converter = self._init_area_reading(local_study_w_thermal) - thermals_components = converter._convert_thermal_to_component_list(areas) + ( + thermals_components, + thermals_connections, + ) = converter._convert_thermal_to_component_list(areas) study_path = converter.study_path p_max_timeserie = str( study_path / "input" / "thermal" / "series" / "fr" / "gaz" / "series.txt" ) + expected_thermals_connections = [ + InputPortConnections( + component1="gaz", + port_1="balance_port", + component2="fr", + port_2="balance_port", + ) + ] expected_thermals_components = [ InputComponent( id="gaz", @@ -340,6 +371,7 @@ def test_convert_thermals_to_input_study(self, local_study_w_thermal): ) ] assert thermals_components == expected_thermals_components + assert thermals_connections == expected_thermals_connections def test_convert_area_to_yaml(self, local_study_w_areas): areas, converter = self._init_area_reading(local_study_w_areas) @@ -406,3 +438,147 @@ def test_convert_area_to_yaml(self, local_study_w_areas): expected_validated_data.nodes.sort(key=lambda x: x.id) validated_data.nodes.sort(key=lambda x: x.id) assert validated_data == expected_validated_data + + def test_convert_solar_to_component(self, local_study_w_areas, fr_solar): + areas, converter = self._init_area_reading(local_study_w_areas) + + solar_components, solar_connection = converter._convert_solar_to_component_list( + areas + ) + study_path = converter.study_path + + solar_timeseries = str( + study_path / "input" / "solar" / "series" / f"solar_fr.txt" + ) + expected_solar_connection = [ + InputPortConnections( + component1="solar", + port_1="balance_port", + component2="fr", + port_2="balance_port", + ) + ] + expected_solar_components = InputComponent( + id="fr", + model="solar", + scenario_group=None, + parameters=[ + InputComponentParameter( + name="solar", + type="timeseries", + scenario_group=None, + value=None, + timeseries=f"{solar_timeseries}", + ), + ], + ) + + assert solar_components[0] == expected_solar_components + assert solar_connection == expected_solar_connection + + def test_convert_load_to_component(self, local_study_w_areas, fr_load): + areas, converter = self._init_area_reading(local_study_w_areas) + + load_components, load_connection = converter._convert_load_to_component_list( + areas + ) + study_path = converter.study_path + + load_timeseries = str(study_path / "input" / "load" / "series" / f"load_fr.txt") + expected_load_connection = [ + InputPortConnections( + component1="load", + port_1="balance_port", + component2="fr", + port_2="balance_port", + ) + ] + expected_load_components = InputComponent( + id="fr", + model="load", + scenario_group=None, + parameters=[ + InputComponentParameter( + name="load", + type="timeseries", + scenario_group=None, + value=None, + timeseries=f"{load_timeseries}", + ), + ], + ) + + assert load_components[0] == expected_load_components + assert load_connection == expected_load_connection + + @pytest.mark.parametrize( + "fr_wind", + [ + [1, 1, 1], # Dataframe filled with 1 + ], + indirect=True, + ) + def test_convert_wind_to_component_not_empty_file( + self, local_study_w_areas, fr_wind + ): + areas, converter = self._init_area_reading(local_study_w_areas) + + wind_components, wind_connection = converter._convert_wind_to_component_list( + areas + ) + study_path = converter.study_path + + wind_timeseries = str(study_path / "input" / "wind" / "series" / f"wind_fr.txt") + expected_wind_connection = [ + InputPortConnections( + component1="wind", + port_1="balance_port", + component2="fr", + port_2="balance_port", + ) + ] + expected_wind_components = InputComponent( + id="fr", + model="wind", + scenario_group=None, + parameters=[ + InputComponentParameter( + name="wind", + type="timeseries", + scenario_group=None, + value=None, + timeseries=f"{wind_timeseries}", + ), + ], + ) + + assert wind_components[0] == expected_wind_components + assert wind_connection == expected_wind_connection + + @pytest.mark.parametrize( + "fr_wind", + [ + [], # DataFrame empty + ], + indirect=True, + ) + def test_convert_wind_to_component_empty_file(self, local_study_w_areas, fr_wind): + areas, converter = self._init_area_reading(local_study_w_areas) + + wind_components, _ = converter._convert_wind_to_component_list(areas) + + assert wind_components == [] + + @pytest.mark.parametrize( + "fr_wind", + [ + [0, 0, 0], # DataFrame full of 0 + ], + indirect=True, + ) + def test_convert_wind_to_component_zero_values(self, local_study_w_areas, fr_wind): + areas, converter = self._init_area_reading(local_study_w_areas) + + wind_components, _ = converter._convert_wind_to_component_list(areas) + + assert wind_components == [] diff --git a/tests/unittests/data/components.yml b/tests/unittests/data/components.yml index b5ae7570..6915ea3a 100644 --- a/tests/unittests/data/components.yml +++ b/tests/unittests/data/components.yml @@ -41,6 +41,3 @@ study: port_1: injection_port component2: G port_2: injection_port - - -