Skip to content

Commit

Permalink
Add ExpectedValueStrategy to Benders
Browse files Browse the repository at this point in the history
  • Loading branch information
ianmnz committed Jul 11, 2024
1 parent 0f50a9b commit 9e92f54
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 11 deletions.
7 changes: 7 additions & 0 deletions src/andromede/simulation/benders_decomposed.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from andromede.simulation.runner import BendersRunner, MergeMPSRunner
from andromede.simulation.strategy import (
ExpectedValue,
InvestmentProblemStrategy,
OperationalProblemStrategy,
)
Expand Down Expand Up @@ -221,6 +222,9 @@ def build_benders_decomposed_problem(
Returns a Benders Decomposed problem
"""

if not decision_tree_root.is_leaves_prob_sum_one():
raise RuntimeError("Decision tree must have leaves' probability sum equal one!")

null_time_block = TimeBlock(
0, [0]
) # Not necessary for master, but list must be non-empty
Expand All @@ -234,6 +238,7 @@ def build_benders_decomposed_problem(
problem_name="coupler",
solver_id=solver_id,
build_strategy=InvestmentProblemStrategy(),
risk_strategy=ExpectedValue(0.0),
)

masters = [] # Benders Decomposed Master Problem
Expand All @@ -252,6 +257,7 @@ def build_benders_decomposed_problem(
solver_id=solver_id,
build_strategy=InvestmentProblemStrategy(),
decision_tree_node=tree_node.id,
risk_strategy=ExpectedValue(tree_node.prob),
)
)

Expand All @@ -269,6 +275,7 @@ def build_benders_decomposed_problem(
solver_id=solver_id,
build_strategy=OperationalProblemStrategy(),
decision_tree_node=tree_node.id,
risk_strategy=ExpectedValue(tree_node.prob),
)
)

Expand Down
27 changes: 21 additions & 6 deletions src/andromede/simulation/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@
from andromede.model.model import PortFieldId
from andromede.simulation.linear_expression import LinearExpression, Term
from andromede.simulation.linearize import linearize_expression
from andromede.simulation.strategy import MergedProblemStrategy, ModelSelectionStrategy
from andromede.simulation.strategy import (
MergedProblemStrategy,
ModelSelectionStrategy,
RiskManagementStrategy,
UniformRisk,
)
from andromede.simulation.time_block import TimeBlock
from andromede.study.data import DataBase
from andromede.study.network import Component, Network
Expand Down Expand Up @@ -290,6 +295,7 @@ def __init__(
scenarios: int,
border_management: BlockBorderManagement,
build_strategy: ModelSelectionStrategy = MergedProblemStrategy(),
risk_strategy: RiskManagementStrategy = UniformRisk(),
decision_tree_node: str = "",
):
self._network = network
Expand All @@ -298,6 +304,7 @@ def __init__(
self._scenarios = scenarios
self._border_management = border_management
self._build_strategy = build_strategy
self._risk_strategy = risk_strategy
self._tree_node = decision_tree_node

self._component_variables: Dict[TimestepComponentVariableKey, lp.Variable] = {}
Expand All @@ -319,9 +326,13 @@ def tree_node(self) -> str:
return self._tree_node

@property
def strategy(self) -> ModelSelectionStrategy:
def build_strategy(self) -> ModelSelectionStrategy:
return self._build_strategy

@property
def risk_strategy(self) -> RiskManagementStrategy:
return self._risk_strategy

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

Expand Down Expand Up @@ -714,7 +725,7 @@ def _create_variables(self) -> None:
component_context = self.context.create_component_context(component)
model = component.model

for model_var in self.context.strategy.get_variables(model):
for model_var in self.context.build_strategy.get_variables(model):
var_indexing = IndexingStructure(
model_var.structure.time, model_var.structure.scenario
)
Expand Down Expand Up @@ -770,7 +781,9 @@ def _create_variables(self) -> None:

def _create_constraints(self) -> None:
for component in self.context.network.all_components:
for constraint in self.context.strategy.get_constraints(component.model):
for constraint in self.context.build_strategy.get_constraints(
component.model
):
instantiated_expr = _instantiate_model_expression(
constraint.expression, component.id, self.context
)
Expand Down Expand Up @@ -798,14 +811,14 @@ def _create_objectives(self) -> None:
component_context = self.context.create_component_context(component)
model = component.model

for objective in self.context.strategy.get_objectives(model):
for objective in self.context.build_strategy.get_objectives(model):
if objective is not None:
_create_objective(
self.solver,
self.context,
component,
component_context,
objective,
self.context.risk_strategy(objective),
)

def export_as_mps(self) -> str:
Expand All @@ -825,6 +838,7 @@ def build_problem(
border_management: BlockBorderManagement = BlockBorderManagement.CYCLE,
solver_id: str = "GLOP",
build_strategy: ModelSelectionStrategy = MergedProblemStrategy(),
risk_strategy: RiskManagementStrategy = UniformRisk(),
decision_tree_node: str = "",
) -> OptimizationProblem:
"""
Expand All @@ -841,6 +855,7 @@ def build_problem(
scenarios,
border_management,
build_strategy,
risk_strategy,
decision_tree_node,
)

Expand Down
33 changes: 32 additions & 1 deletion src/andromede/simulation/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from abc import ABC, abstractmethod
from typing import Generator, Optional

from andromede.expression import ExpressionNode
from andromede.expression import ExpressionNode, literal
from andromede.model import Constraint, Model, ProblemContext, Variable


Expand Down Expand Up @@ -80,3 +80,34 @@ def get_objectives(
self, model: Model
) -> Generator[Optional[ExpressionNode], None, None]:
yield model.objective_operational_contribution


class RiskManagementStrategy(ABC):
"""
Abstract functor class for risk management
Its derived classes will implement risk measures:
- UniformRisk : The default case. All expressions have the same weight
- ExpectedValue : Computes the product prob * expression
TODO For now, it will only take into account the Expected Value
TODO In the future could have other risk measures?
"""

def __call__(self, expr: ExpressionNode) -> ExpressionNode:
return self._modify_expression(expr)

@abstractmethod
def _modify_expression(self, expr: ExpressionNode) -> ExpressionNode:
...


class UniformRisk(RiskManagementStrategy):
def _modify_expression(self, expr: ExpressionNode) -> ExpressionNode:
return expr


class ExpectedValue(RiskManagementStrategy):
def __init__(self, prob: float) -> None:
self._prob = prob

def _modify_expression(self, expr: ExpressionNode) -> ExpressionNode:
return literal(self._prob) * expr
5 changes: 1 addition & 4 deletions tests/functional/test_investment_pathway.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,6 @@ def test_investment_pathway_on_a_tree_with_one_root_two_children(
- 100 in child B
"""

# Either we duplicate all network for each node : Lots of duplications
# or we index all data, parameters, variables by the resolution node : Make the data structure dependent of the resolution tree...

database = DataBase()
database.add_data("N", "spillage_cost", ConstantData(10))
database.add_data("N", "ens_cost", ConstantData(10_000))
Expand Down Expand Up @@ -458,7 +455,7 @@ def test_investment_pathway_on_a_tree_with_one_root_two_children(

data = {
"solution": {
"overall_cost": 49_000,
"overall_cost": 39_200,
"values": {
"root_CAND_delta_invest": 300,
"childA_CAND_delta_invest": 0,
Expand Down

0 comments on commit 9e92f54

Please sign in to comment.