Skip to content

contrib: Finish st storage components, setup end to end test #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ absl-py==2.1.0
# via ortools
annotated-types==0.7.0
# via pydantic
antares-craft==0.2.1
antares-craft==0.2.3
# via -r requirements.in
antares-study-version==1.0.9
# via antares-craft
Expand Down
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ scipy==1.10.1
antlr4-python3-runtime==4.13.1
PyYAML~=6.0.1
pydantic
antares_craft>=0.2.1
antares_craft>=0.2.3
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ absl-py==2.1.0
# via ortools
annotated-types==0.7.0
# via pydantic
antares-craft==0.2.1
antares-craft==0.2.3
# via -r requirements.in
antares-study-version==1.0.9
# via antares-craft
Expand Down
112 changes: 111 additions & 1 deletion src/andromede/input_converter/src/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# This file is part of the Antares project.
import logging
from pathlib import Path
from typing import Optional, Union, Iterable
from typing import Iterable, Optional, Union

from antares.craft.model.area import Area
from antares.craft.model.study import Study, read_study_local
Expand Down Expand Up @@ -258,6 +258,115 @@ def _convert_thermal_to_component_list(
)
return components, connections

def _convert_st_storage_to_component_list(
self, areas: Iterable[Area]
) -> 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="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
# InputComponentParameter(
# id="efficiency_withdrawal",
# time_dependent=False,
# scenario_dependent=False,
# value=storage.properties.efficiencywithdrawal,
# ),
InputComponentParameter(
id="initial_level",
time_dependent=False,
scenario_dependent=False,
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,
) -> tuple[list[InputComponent], list[InputPortConnections]]:
Expand Down Expand Up @@ -446,6 +555,7 @@ def convert_study_to_input_study(self) -> InputStudy:
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,
Expand Down
31 changes: 31 additions & 0 deletions tests/antares_historic/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# This file is part of the Antares project.
import pytest
from antares.craft.model.area import AreaProperties
from antares.craft.model.st_storage import STStorageProperties
from antares.craft.model.study import Study, create_study_local
from antares.craft.model.thermal import (
LawOption,
Expand Down Expand Up @@ -105,3 +106,33 @@ def local_study_end_to_end_w_thermal(local_study, default_thermal_cluster_proper
thermal_name, properties=default_thermal_cluster_properties
)
return local_study


@pytest.fixture
def default_st_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_end_to_end_w_thermal, default_st_storage_cluster_properties
) -> Study:
"""
Create an empty study
Create an area with custom area properties
Create a thermal cluster with custom thermal properties
Create a short term storage
"""
storage_name = "battery"
local_study_end_to_end_w_thermal.get_areas()["fr"].create_st_storage(
storage_name, properties=default_st_storage_cluster_properties
)
return local_study_end_to_end_w_thermal
66 changes: 47 additions & 19 deletions tests/antares_historic/test_antares_historic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from dataclasses import dataclass
from pathlib import Path

import pandas as pd
Expand All @@ -8,6 +9,7 @@
from andromede.model.parsing import InputLibrary, parse_yaml_library
from andromede.model.resolve_library import resolve_library
from andromede.simulation import TimeBlock, build_problem
from andromede.simulation.optimization import OptimizationProblem
from andromede.study.data import load_ts_from_txt
from andromede.study.parsing import InputStudy, parse_yaml_components
from andromede.study.resolve_components import (
Expand All @@ -18,6 +20,12 @@
)


@dataclass(frozen=True)
class ToolTestStudy:
study_component_data: InputStudy
study_path: Path


def create_csv_from_constant_value(
path, filename: str, lines: int, columns: int = 1, value: float = 1
) -> None:
Expand Down Expand Up @@ -124,7 +132,7 @@ def fill_timeseries(study_path) -> None:
create_csv_from_constant_value(path=series_path, filename="nb_units_max", lines=3)


def _setup_study_component(study, period=None) -> tuple:
def _setup_study_component(study, period=None) -> ToolTestStudy:
"""
Helper function to reduce redundancy in study component setup.
"""
Expand All @@ -142,19 +150,24 @@ def _setup_study_component(study, period=None) -> tuple:

compo_file = converter.output_path
with compo_file.open() as c:
return parse_yaml_components(c), study_path
return ToolTestStudy(parse_yaml_components(c), study_path)


@pytest.fixture
def study_component_basic(local_study_end_to_end_simple) -> tuple:
def study_component_basic(local_study_end_to_end_simple) -> ToolTestStudy:
return _setup_study_component(local_study_end_to_end_simple)


@pytest.fixture
def study_component_thermal(local_study_end_to_end_w_thermal) -> tuple:
def study_component_thermal(local_study_end_to_end_w_thermal) -> ToolTestStudy:
return _setup_study_component(local_study_end_to_end_w_thermal, period=3)


@pytest.fixture
def study_component_st_storage(local_study_with_st_storage) -> ToolTestStudy:
return _setup_study_component(local_study_with_st_storage, period=3)


@pytest.fixture
def input_library(
data_dir: Path,
Expand All @@ -171,18 +184,18 @@ def input_library(
return parse_yaml_library(lib)


def factory_balance_using_converter(
study_component: InputStudy, input_library: InputLibrary, expected_value: int
) -> None:
def problem_builder(
study_test_component: ToolTestStudy, input_library: InputLibrary
) -> OptimizationProblem:
"""
- Resolves the input library.
- Constructs components and connections.
- Performs consistency checks.
- Builds the database and network.
- Solves the optimization problem and verifies results.
"""
study_path = study_component[1]
study_component_data = study_component[0]
study_path = study_test_component.study_path
study_component_data = study_test_component.study_component_data

result_lib = resolve_library([input_library])
components_input = resolve_components_and_cnx(study_component_data, result_lib)
Expand All @@ -192,10 +205,7 @@ def factory_balance_using_converter(
network = build_network(components_input)

scenarios = 1
problem = build_problem(network, database, TimeBlock(1, [0, 1]), scenarios)
status = problem.solver.Solve()
assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == expected_value
return build_problem(network, database, TimeBlock(1, [0, 1]), scenarios)


def test_basic_balance_using_converter(
Expand All @@ -204,9 +214,10 @@ def test_basic_balance_using_converter(
"""
Test basic study balance using the converter.
"""
factory_balance_using_converter(
study_component_basic, input_library, expected_value=150
)
problem = problem_builder(study_component_basic, input_library)
status = problem.solver.Solve()
assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 150


def test_thermal_balance_using_converter(
Expand All @@ -215,6 +226,23 @@ def test_thermal_balance_using_converter(
"""
Test thermal study balance using the converter.
"""
factory_balance_using_converter(
study_component_thermal, input_library, expected_value=165
)
problem = problem_builder(study_component_thermal, input_library)

status = problem.solver.Solve()
assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 165


@pytest.mark.skip(reason="Test temporarily deactivated")
def test_storage_balance_using_converter(
study_component_st_storage: InputStudy, input_library: InputLibrary
) -> None:
"""
Test storage study balance using the converter.
"""
# Wait for new version 0.92 of antares craft which include efficiencywithdrawalparameter
problem = problem_builder(study_component_st_storage, input_library)

status = problem.solver.Solve()
assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 165
24 changes: 21 additions & 3 deletions tests/input_converter/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -134,7 +135,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
Expand All @@ -143,8 +159,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


Expand Down
Loading
Loading