diff --git a/src/antares/model/area.py b/src/antares/model/area.py index 346fea37..a805aa75 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -30,6 +30,7 @@ from antares.model.reserves import Reserves from antares.model.st_storage import STStorage, STStorageProperties from antares.model.thermal import ThermalCluster, ThermalClusterProperties +from antares.model.wind import Wind from antares.tools.contents_tool import transform_name_to_id, EnumIgnoreCase @@ -242,6 +243,7 @@ def __init__( # type: ignore # TODO: Find a way to avoid circular imports thermals: Optional[Dict[str, ThermalCluster]] = None, st_storages: Optional[Dict[str, STStorage]] = None, hydro: Optional[Hydro] = None, + wind: Optional[Wind] = None, reserves: Optional[Reserves] = None, misc_gen: Optional[MiscGen] = None, properties: Optional[AreaProperties] = None, @@ -257,6 +259,7 @@ def __init__( # type: ignore # TODO: Find a way to avoid circular imports self._thermals = thermals or dict() self._st_storages = st_storages or dict() self._hydro = hydro + self._wind = wind self._reserves = reserves self._misc_gen = misc_gen self._properties = properties or AreaProperties() @@ -365,8 +368,10 @@ def update_ui(self, ui: AreaUi) -> None: new_ui = self._area_service.update_area_ui(self, ui) self._ui = new_ui - def create_wind(self, series: Optional[pd.DataFrame]) -> None: - self._area_service.create_wind(self, series) + def create_wind(self, series: Optional[pd.DataFrame]) -> Wind: + wind = self._area_service.create_wind(self, series) + self._wind = wind + return wind def create_reserves(self, series: Optional[pd.DataFrame]) -> Reserves: reserves = self._area_service.create_reserves(self, series) diff --git a/src/antares/model/wind.py b/src/antares/model/wind.py new file mode 100644 index 00000000..6ccd6a81 --- /dev/null +++ b/src/antares/model/wind.py @@ -0,0 +1,18 @@ +# 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 antares.tools.time_series_tool import TimeSeries + + +class Wind(TimeSeries): + pass diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index eda6ffeb..ba28b595 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -42,6 +42,7 @@ from antares.model.reserves import Reserves from antares.model.st_storage import STStorageProperties, STStorage from antares.model.thermal import ThermalClusterProperties, ThermalCluster +from antares.model.wind import Wind from antares.service.base_services import ( BaseAreaService, BaseShortTermStorageService, @@ -364,9 +365,11 @@ def _upload_series(self, area: Area, series: Optional[pd.DataFrame], path: str) except APIError as e: raise MatrixUploadError(area.id, e.message) from e - def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> None: + def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> Wind: + series = series if series is not None else pd.DataFrame([]) series_path = f"input/wind/series/wind_{area.id}" self._upload_series(area, series, series_path) + return Wind(series) def create_reserves(self, area: Area, series: Optional[pd.DataFrame]) -> Reserves: series = series if series is not None else pd.DataFrame([]) diff --git a/src/antares/service/base_services.py b/src/antares/service/base_services.py index 646d2437..8187c022 100644 --- a/src/antares/service/base_services.py +++ b/src/antares/service/base_services.py @@ -32,6 +32,7 @@ from antares.model.settings import StudySettings from antares.model.st_storage import STStorageProperties, STStorage from antares.model.thermal import ThermalClusterProperties, ThermalCluster, ThermalClusterMatrixName +from antares.model.wind import Wind class BaseAreaService(ABC): @@ -135,11 +136,11 @@ def create_st_storage( pass @abstractmethod - def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> None: + def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> Wind: """ Args: area: area to create wind series matrices - series: wind/series/series.txt + series: wind/series/wind_{area_id}.txt """ pass diff --git a/src/antares/service/local_services/area_local.py b/src/antares/service/local_services/area_local.py index ccec73c4..4170d042 100644 --- a/src/antares/service/local_services/area_local.py +++ b/src/antares/service/local_services/area_local.py @@ -26,6 +26,7 @@ from antares.model.reserves import Reserves from antares.model.st_storage import STStorageProperties, STStorage, STStoragePropertiesLocal from antares.model.thermal import ThermalClusterProperties, ThermalCluster, ThermalClusterPropertiesLocal +from antares.model.wind import Wind from antares.service.base_services import ( BaseAreaService, BaseShortTermStorageService, @@ -130,8 +131,10 @@ def create_st_storage( local_st_storage_properties.yield_st_storage_properties(), ) - def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> None: - raise NotImplementedError + def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> Wind: + series = series if series is not None else pd.DataFrame([]) + local_file = TimeSeriesFile(TimeSeriesFileType.WIND, self.config.study_path, area.id, series) + return Wind(series, local_file) def create_reserves(self, area: Area, series: Optional[pd.DataFrame]) -> Reserves: series = series if series is not None else pd.DataFrame([]) diff --git a/src/antares/tools/time_series_tool.py b/src/antares/tools/time_series_tool.py index 4ceaabf0..bb1ed6d1 100644 --- a/src/antares/tools/time_series_tool.py +++ b/src/antares/tools/time_series_tool.py @@ -28,6 +28,7 @@ class TimeSeriesFileType(Enum): MISC_GEN = "input/misc-gen/miscgen-{area_id}.txt" RESERVES = "input/reserves/{area_id}.txt" + WIND = "input/wind/series/wind_{area_id}.txt" class TimeSeriesFile: diff --git a/tests/antares/services/local_services/test_area.py b/tests/antares/services/local_services/test_area.py index c7ba22ef..8b92dc13 100644 --- a/tests/antares/services/local_services/test_area.py +++ b/tests/antares/services/local_services/test_area.py @@ -714,8 +714,45 @@ def test_can_create_misc_gen_ts_file_with_time_series(self, area_fr): # When area_fr.create_misc_gen(pd.DataFrame(np.ones([2, 3]))) actual_time_series = pd.read_csv(misc_gen_file_path, sep="\t", header=None) - with misc_gen_file_path.open("r") as reserves_ts_file: - actual_time_series_string = reserves_ts_file.read() + with misc_gen_file_path.open("r") as misc_gen_ts_file: + actual_time_series_string = misc_gen_ts_file.read() + + # Then + assert actual_time_series.equals(expected_time_series) + assert actual_time_series_string == expected_time_series_string + + +class TestCreateWind: + def test_can_create_wind_ts_file(self, area_fr): + # Given + wind_file_path = area_fr._area_service.config.study_path / TimeSeriesFileType.WIND.value.format( + area_id=area_fr.id + ) + expected_wind_file_path = area_fr._area_service.config.study_path / "input/wind/series/wind_fr.txt" + + # When + area_fr.create_wind(None) + + # Then + assert wind_file_path == expected_wind_file_path + assert wind_file_path.exists() + assert wind_file_path.is_file() + + def test_can_create_wind_ts_file_with_time_series(self, area_fr): + # Given + wind_file_path = area_fr._area_service.config.study_path / TimeSeriesFileType.WIND.value.format( + area_id=area_fr.id + ) + expected_time_series_string = """1.0\t1.0\t1.0 +1.0\t1.0\t1.0 +""" + expected_time_series = pd.read_csv(StringIO(expected_time_series_string), sep="\t", header=None) + + # When + area_fr.create_wind(pd.DataFrame(np.ones([2, 3]))) + actual_time_series = pd.read_csv(wind_file_path, sep="\t", header=None) + with wind_file_path.open("r") as wind_ts_file: + actual_time_series_string = wind_ts_file.read() # Then assert actual_time_series.equals(expected_time_series)