Skip to content

Commit

Permalink
Change scenario type in build problem
Browse files Browse the repository at this point in the history
  • Loading branch information
Juliette-Gerbaux committed Aug 21, 2024
1 parent 9a6e24f commit 696f746
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 68 deletions.
7 changes: 6 additions & 1 deletion src/andromede/simulation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
BendersDecomposedProblem,
build_benders_decomposed_problem,
)
from .optimization import BlockBorderManagement, OptimizationProblem, build_problem
from .optimization import (
BlockBorderManagement,
OptimizationProblem,
build_problem,
scenario_playlist,
)
from .output_values import BendersSolution, OutputValues
from .runner import BendersRunner, MergeMPSRunner
from .strategy import MergedProblemStrategy, ModelSelectionStrategy
Expand Down
5 changes: 3 additions & 2 deletions src/andromede/simulation/benders_decomposed.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def build_benders_decomposed_problem(
network: Network,
database: DataBase,
blocks: List[TimeBlock],
scenarios: int,
scenarios: List[int],
*,
border_management: BlockBorderManagement = BlockBorderManagement.CYCLE,
solver_id: str = "GLOP",
Expand All @@ -217,6 +217,7 @@ def build_benders_decomposed_problem(
Returns a Benders Decomposed problem
"""
null_scenario: List[int] = []

# Benders Decomposed Master Problem
master = build_problem(
Expand All @@ -225,7 +226,7 @@ def build_benders_decomposed_problem(
null_time_block := TimeBlock( # Not necessary for master, but list must be non-empty
0, [0]
),
null_scenario := 0, # Not necessary for master
null_scenario, # Not necessary for master
problem_name="master",
border_management=border_management,
solver_id=solver_id,
Expand Down
32 changes: 23 additions & 9 deletions src/andromede/simulation/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
from typing import Dict, Iterable, List, Optional
from typing import Dict, Iterable, List, Optional, Union

import ortools.linear_solver.pywraplp as lp

Expand Down Expand Up @@ -69,8 +69,9 @@ def _get_parameter_value(
name: str,
) -> float:
data = context.database.get_data(component_id, name)
absolute_scenario = context.scenario_to_absolute_scenario(scenario)
absolute_timestep = context.block_timestep_to_absolute_timestep(block_timestep)
return data.get_value(absolute_timestep, scenario)
return data.get_value(absolute_timestep, absolute_scenario)


# TODO: Maybe add the notion of constant parameter in the model
Expand Down Expand Up @@ -304,7 +305,7 @@ def __init__(
network: Network,
database: DataBase,
block: TimeBlock,
scenarios: int,
scenarios: List[int],
border_management: BlockBorderManagement,
):
self._network = network
Expand All @@ -323,9 +324,12 @@ def network(self) -> Network:
return self._network

@property
def scenarios(self) -> int:
def scenarios(self) -> List[int]:
return self._scenarios

def scenario_length(self) -> int:
return len(self._scenarios)

def block_length(self) -> int:
return len(self._block.timesteps)

Expand All @@ -337,6 +341,12 @@ def connection_fields_expressions(self) -> Dict[PortFieldKey, List[ExpressionNod
def block_timestep_to_absolute_timestep(self, block_timestep: int) -> int:
return self._block.timesteps[block_timestep]

def scenario_to_absolute_scenario(self, scenario: int) -> int:
if len(self.scenarios) == 0:
return 0
else:
return self.scenarios[scenario]

@property
def database(self) -> DataBase:
return self._database
Expand All @@ -351,7 +361,7 @@ def get_time_indices(self, index_structure: IndexingStructure) -> Iterable[int]:
return range(self.block_length()) if index_structure.time else range(1)

def get_scenario_indices(self, index_structure: IndexingStructure) -> Iterable[int]:
return range(self.scenarios) if index_structure.scenario else range(1)
return range(self.scenario_length()) if index_structure.scenario else range(1)

# TODO: API to improve, variable_structure guides which of the indices block_timestep and scenario should be used
def get_component_variable(
Expand Down Expand Up @@ -509,8 +519,8 @@ def _create_objective(
for term in linear_expr.terms.values():
# TODO : How to handle the scenario operator in a general manner ?
if isinstance(term.scenario_operator, Expectation):
weight = 1 / opt_context.scenarios
scenario_ids = range(opt_context.scenarios)
weight = 1 / opt_context.scenario_length()
scenario_ids = range(opt_context.scenario_length())
else:
weight = 1
scenario_ids = range(1)
Expand Down Expand Up @@ -761,7 +771,7 @@ def _create_variables(self) -> None:
scenario_suffix = (
f"_s{scenario}"
if var_indexing.is_scenario_varying()
and (self.context.scenarios > 1)
and (self.context.scenario_length() > 1)
else ""
)

Expand Down Expand Up @@ -853,7 +863,7 @@ def build_problem(
network: Network,
database: DataBase,
block: TimeBlock,
scenarios: int,
scenarios: List[int],
*,
problem_name: str = "optimization_problem",
border_management: BlockBorderManagement = BlockBorderManagement.CYCLE,
Expand All @@ -872,3 +882,7 @@ def build_problem(
)

return OptimizationProblem(problem_name, solver, opt_context, problem_strategy)


def scenario_playlist(scenarios: int) -> List[int]:
return list(range(scenarios))
17 changes: 11 additions & 6 deletions tests/functional/test_andromede.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
OutputValues,
TimeBlock,
build_problem,
scenario_playlist,
)
from andromede.study import (
ConstantData,
Expand Down Expand Up @@ -89,7 +90,7 @@ def test_timeseries() -> None:
time_block = TimeBlock(1, [0, 1])
scenarios = 1

problem = build_problem(network, database, time_block, scenarios)
problem = build_problem(network, database, time_block, scenario_playlist(scenarios))
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -161,15 +162,15 @@ def test_variable_bound() -> None:

network = create_one_node_network(generator_model)
database = create_simple_database(max_generation=200)
problem = build_problem(network, database, TimeBlock(1, [0]), 1)
problem = build_problem(network, database, TimeBlock(1, [0]), scenario_playlist(1))
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
assert problem.solver.Objective().Value() == 3000

network = create_one_node_network(generator_model)
database = create_simple_database(max_generation=80)
problem = build_problem(network, database, TimeBlock(1, [0]), 1)
problem = build_problem(network, database, TimeBlock(1, [0]), scenario_playlist(1))
status = problem.solver.Solve()
assert status == problem.solver.INFEASIBLE # Infeasible

Expand All @@ -179,15 +180,19 @@ def test_variable_bound() -> None:
ValueError,
match="Upper and lower bounds of variable G_generation have the same value: 0",
):
problem = build_problem(network, database, TimeBlock(1, [0]), 1)
problem = build_problem(
network, database, TimeBlock(1, [0]), scenario_playlist(1)
)

network = create_one_node_network(generator_model)
database = create_simple_database(max_generation=-10)
with pytest.raises(
ValueError,
match=r"Upper bound \(-10\) must be strictly greater than lower bound \(0\) for variable G_generation",
):
problem = build_problem(network, database, TimeBlock(1, [0]), 1)
problem = build_problem(
network, database, TimeBlock(1, [0]), scenario_playlist(1)
)


def generate_data(
Expand Down Expand Up @@ -251,7 +256,7 @@ def short_term_storage_base(efficiency: float, horizon: int) -> None:
network,
database,
time_blocks[0],
scenarios,
scenario_playlist(scenarios),
border_management=BlockBorderManagement.CYCLE,
)
status = problem.solver.Solve()
Expand Down
21 changes: 14 additions & 7 deletions tests/functional/test_andromede_yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
OutputValues,
TimeBlock,
build_problem,
scenario_playlist,
)
from andromede.study import (
ConstantData,
Expand Down Expand Up @@ -82,7 +83,9 @@ def test_basic_balance(lib: Library) -> None:
network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port"))

scenarios = 1
problem = build_problem(network, database, TimeBlock(1, [0]), scenarios)
problem = build_problem(
network, database, TimeBlock(1, [0]), scenario_playlist(scenarios)
)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -134,7 +137,9 @@ def test_link(lib: Library) -> None:
network.connect(PortRef(link, "out_port"), PortRef(node2, "balance_port"))

scenarios = 1
problem = build_problem(network, database, TimeBlock(1, [0]), scenarios)
problem = build_problem(
network, database, TimeBlock(1, [0]), scenario_playlist(scenarios)
)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -192,7 +197,9 @@ def test_stacking_generation(lib: Library) -> None:
network.connect(PortRef(gen2, "balance_port"), PortRef(node1, "balance_port"))

scenarios = 1
problem = build_problem(network, database, TimeBlock(1, [0]), scenarios)
problem = build_problem(
network, database, TimeBlock(1, [0]), scenario_playlist(scenarios)
)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -232,7 +239,7 @@ def test_spillage(lib: Library) -> None:
network.connect(PortRef(gen1, "balance_port"), PortRef(node, "balance_port"))
network.connect(PortRef(spillage, "balance_port"), PortRef(node, "balance_port"))

problem = build_problem(network, database, TimeBlock(0, [1]), 1)
problem = build_problem(network, database, TimeBlock(0, [1]), scenario_playlist(1))
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -322,7 +329,7 @@ def test_min_up_down_times(lib: Library) -> None:
network,
database,
time_block,
scenarios,
scenario_playlist(scenarios),
border_management=BlockBorderManagement.CYCLE,
)
status = problem.solver.Solve()
Expand Down Expand Up @@ -380,7 +387,7 @@ def test_changing_demand(lib: Library) -> None:
network,
database,
time_block,
scenarios,
scenario_playlist(scenarios),
border_management=BlockBorderManagement.CYCLE,
)
status = problem.solver.Solve()
Expand Down Expand Up @@ -472,7 +479,7 @@ def test_min_up_down_times_2(lib: Library) -> None:
network,
database,
time_block,
scenarios,
scenario_playlist(scenarios),
border_management=BlockBorderManagement.CYCLE,
)
status = problem.solver.Solve()
Expand Down
22 changes: 15 additions & 7 deletions tests/functional/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
NODE_BALANCE_MODEL,
)
from andromede.model import float_parameter, float_variable, model
from andromede.simulation import TimeBlock, build_problem
from andromede.simulation import TimeBlock, build_problem, scenario_playlist
from andromede.study import (
ConstantData,
DataBase,
Expand Down Expand Up @@ -70,7 +70,9 @@ def test_large_sum_inside_model_with_loop() -> None:
cost_model = create_component(model=SIMPLE_COST_MODEL, id="simple_cost")
network.add_component(cost_model)

problem = build_problem(network, database, time_blocks[0], scenarios)
problem = build_problem(
network, database, time_blocks[0], scenario_playlist(scenarios)
)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -106,7 +108,9 @@ def test_large_sum_outside_model_with_loop() -> None:
)
network.add_component(simple_model)

problem = build_problem(network, database, time_blocks[0], scenarios)
problem = build_problem(
network, database, time_blocks[0], scenario_playlist(scenarios)
)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -151,7 +155,9 @@ def test_large_sum_inside_model_with_sum_operator() -> None:
cost_model = create_component(model=SIMPLE_COST_MODEL, id="simple_cost")
network.add_component(cost_model)

problem = build_problem(network, database, time_blocks[0], scenarios)
problem = build_problem(
network, database, time_blocks[0], scenario_playlist(scenarios)
)
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -197,7 +203,9 @@ def test_large_sum_of_port_connections() -> None:
)

with pytest.raises(RecursionError, match="maximum recursion depth exceeded"):
problem = build_problem(network, database, time_block, scenarios)
problem = build_problem(
network, database, time_block, scenario_playlist(scenarios)
)

# Won't run because last statement will raise the error
status = problem.solver.Solve()
Expand Down Expand Up @@ -235,7 +243,7 @@ def test_basic_balance_on_whole_year() -> None:
network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port"))
network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port"))

problem = build_problem(network, database, time_block, scenarios)
problem = build_problem(network, database, time_block, scenario_playlist(scenarios))
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down Expand Up @@ -273,7 +281,7 @@ def test_basic_balance_on_whole_year_with_large_sum() -> None:
network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port"))
network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port"))

problem = build_problem(network, database, time_block, scenarios)
problem = build_problem(network, database, time_block, scenario_playlist(scenarios))
status = problem.solver.Solve()

assert status == problem.solver.OPTIMAL
Expand Down
6 changes: 3 additions & 3 deletions tests/functional/test_stochastic.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
THERMAL_CLUSTER_MODEL_DHD,
THERMAL_CLUSTER_MODEL_HD,
)
from andromede.simulation import TimeBlock, build_problem
from andromede.simulation import TimeBlock, build_problem, scenario_playlist
from andromede.study import (
ConstantData,
DataBase,
Expand Down Expand Up @@ -123,7 +123,7 @@ def test_stochastic_model_with_HD_for_thermal_startup(
network.connect(PortRef(peak, "balance_port"), PortRef(node, "balance_port"))

for block in time_blocks: # TODO : To manage blocks simply for now
problem = build_problem(network, database, block, scenarios)
problem = build_problem(network, database, block, scenario_playlist(scenarios))
status = problem.solver.Solve()

assert (
Expand Down Expand Up @@ -189,7 +189,7 @@ def test_stochastic_model_with_DH_for_thermal_startup(
network.connect(PortRef(peak, "balance_port"), PortRef(node, "balance_port"))

for block in time_blocks: # TODO : To manage blocks simply for now
problem = build_problem(network, database, block, scenarios)
problem = build_problem(network, database, block, scenario_playlist(scenarios))
status = problem.solver.Solve()

assert (
Expand Down
Loading

0 comments on commit 696f746

Please sign in to comment.