diff --git a/src/andromede/model/__init__.py b/src/andromede/model/__init__.py index 043fd48c..6aee655b 100644 --- a/src/andromede/model/__init__.py +++ b/src/andromede/model/__init__.py @@ -12,15 +12,7 @@ from .common import ProblemContext, ValueType from .constraint import Constraint -from .model import ( - InvestmentProblemStrategy, - MergedProblemStrategy, - Model, - ModelPort, - ModelSelectionStrategy, - OperationalProblemStrategy, - model, -) +from .model import Model, ModelPort, model from .parameter import Parameter, float_parameter, int_parameter from .port import PortField, PortType from .variable import Variable, float_variable, int_variable diff --git a/src/andromede/model/model.py b/src/andromede/model/model.py index 9d6774a7..3997f43d 100644 --- a/src/andromede/model/model.py +++ b/src/andromede/model/model.py @@ -16,9 +16,8 @@ defining parameters, variables, and equations. """ import itertools -from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import Dict, Generator, Iterable, Optional +from typing import Dict, Iterable, Optional from andromede.expression import ( AdditionNode, @@ -47,7 +46,6 @@ from andromede.expression.indexing import IndexingStructureProvider, compute_indexation from andromede.expression.indexing_structure import IndexingStructure from andromede.expression.visitor import T, visit -from andromede.model.common import ProblemContext from andromede.model.constraint import Constraint from andromede.model.parameter import Parameter from andromede.model.port import PortType @@ -186,81 +184,6 @@ def get_all_constraints(self) -> Iterable[Constraint]: ) -class ModelSelectionStrategy(ABC): - """ - Abstract class to specify the strategy of the created problem. - Its derived classes select variables and constraints for the optimization problems: - - InvestmentProblemStrategy: Keep investment and coupling variables and constraints only for a BendersDecomposed master - - OperationalProblemStrategy: Keep operational and coupling variables and constraints only for a BendersDecomposed sub-problems - - MergedProblemStrategy: Keep all variables and constraints - """ - - @classmethod - def get_variables(cls, model: Model) -> Generator[Variable, None, None]: - for variable in model.variables.values(): - if cls._keep_from_context(variable.context): - yield variable - - @classmethod - def get_constraints(cls, model: Model) -> Generator[Constraint, None, None]: - for constraint in model.get_all_constraints(): - if cls._keep_from_context(constraint.context): - yield constraint - - @classmethod - @abstractmethod - def _keep_from_context(cls, context: ProblemContext) -> bool: - ... - - @classmethod - @abstractmethod - def get_objectives( - cls, model: Model - ) -> Generator[Optional[ExpressionNode], None, None]: - ... - - -class MergedProblemStrategy(ModelSelectionStrategy): - @classmethod - def _keep_from_context(cls, context: ProblemContext) -> bool: - return True - - @classmethod - def get_objectives( - cls, model: Model - ) -> Generator[Optional[ExpressionNode], None, None]: - yield model.objective_operational_contribution - yield model.objective_investment_contribution - - -class InvestmentProblemStrategy(ModelSelectionStrategy): - @classmethod - def _keep_from_context(cls, context: ProblemContext) -> bool: - return ( - context == ProblemContext.INVESTMENT or context == ProblemContext.COUPLING - ) - - @classmethod - def get_objectives( - cls, model: Model - ) -> Generator[Optional[ExpressionNode], None, None]: - yield model.objective_investment_contribution - - -class OperationalProblemStrategy(ModelSelectionStrategy): - @classmethod - def _keep_from_context(cls, context: ProblemContext) -> bool: - return ( - context == ProblemContext.OPERATIONAL or context == ProblemContext.COUPLING - ) - - @classmethod - def get_objectives( - cls, model: Model - ) -> Generator[Optional[ExpressionNode], None, None]: - yield model.objective_operational_contribution - - def model( id: str, constraints: Optional[Iterable[Constraint]] = None, diff --git a/src/andromede/simulation/__init__.py b/src/andromede/simulation/__init__.py index 1da67920..afdb1dc4 100644 --- a/src/andromede/simulation/__init__.py +++ b/src/andromede/simulation/__init__.py @@ -16,4 +16,5 @@ ) from .optimization import BlockBorderManagement, OptimizationProblem, build_problem from .output_values import OutputValues +from .strategy import MergedProblemStrategy, ModelSelectionStrategy from .time_block import TimeBlock diff --git a/src/andromede/simulation/benders_decomposed.py b/src/andromede/simulation/benders_decomposed.py index 7bae16a9..e9dd9a6a 100644 --- a/src/andromede/simulation/benders_decomposed.py +++ b/src/andromede/simulation/benders_decomposed.py @@ -22,12 +22,15 @@ import sys from typing import Any, Dict, List -from andromede.model.model import InvestmentProblemStrategy, OperationalProblemStrategy from andromede.simulation.optimization import ( BlockBorderManagement, OptimizationProblem, build_problem, ) +from andromede.simulation.strategy import ( + InvestmentProblemStrategy, + OperationalProblemStrategy, +) from andromede.simulation.time_block import TimeBlock from andromede.study.data import DataBase from andromede.study.network import Network @@ -36,7 +39,7 @@ class BendersDecomposedProblem: """ - A simpler interface for the Xpansion problem + A simpler interface for the Benders Decomposed problem """ master: OptimizationProblem @@ -186,10 +189,10 @@ def build_benders_decomposed_problem( """ Entry point to build the xpansion problem for a time period - Returns a Xpansion problem + Returns a Benders Decomposed problem """ - # Xpansion Master Problem + # Benders Decomposed Master Problem master = build_problem( network, database, @@ -198,10 +201,10 @@ def build_benders_decomposed_problem( problem_name="master", border_management=border_management, solver_id=solver_id, - problem_strategy=InvestmentProblemStrategy, + problem_strategy=InvestmentProblemStrategy(), ) - # Xpansion Sub-problems + # Benders Decomposed Sub-problems subproblem = build_problem( network, database, @@ -210,7 +213,7 @@ def build_benders_decomposed_problem( problem_name="subproblem", border_management=border_management, solver_id=solver_id, - problem_strategy=OperationalProblemStrategy, + problem_strategy=OperationalProblemStrategy(), ) return BendersDecomposedProblem(master, [subproblem]) diff --git a/src/andromede/simulation/optimization.py b/src/andromede/simulation/optimization.py index 82eaa321..7e26dd38 100644 --- a/src/andromede/simulation/optimization.py +++ b/src/andromede/simulation/optimization.py @@ -36,15 +36,11 @@ from andromede.expression.port_resolver import PortFieldKey, resolve_port from andromede.expression.scenario_operator import Expectation from andromede.expression.time_operator import TimeEvaluation, TimeShift, TimeSum -from andromede.model.common import ProblemContext from andromede.model.constraint import Constraint -from andromede.model.model import ( - MergedProblemStrategy, - ModelSelectionStrategy, - PortFieldId, -) +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.time_block import TimeBlock from andromede.study.data import DataBase from andromede.study.network import Component, Network @@ -671,14 +667,14 @@ class OptimizationProblem: name: str solver: lp.Solver context: OptimizationContext - strategy: Type[ModelSelectionStrategy] + strategy: ModelSelectionStrategy def __init__( self, name: str, solver: lp.Solver, opt_context: OptimizationContext, - build_strategy: Type[ModelSelectionStrategy] = MergedProblemStrategy, + build_strategy: ModelSelectionStrategy = MergedProblemStrategy(), ) -> None: self.name = name self.solver = solver @@ -819,7 +815,7 @@ def build_problem( problem_name: str = "optimization_problem", border_management: BlockBorderManagement = BlockBorderManagement.CYCLE, solver_id: str = "GLOP", - problem_strategy: Type[ModelSelectionStrategy] = MergedProblemStrategy, + problem_strategy: ModelSelectionStrategy = MergedProblemStrategy(), ) -> OptimizationProblem: """ Entry point to build the optimization problem for a time period. diff --git a/src/andromede/simulation/strategy.py b/src/andromede/simulation/strategy.py new file mode 100644 index 00000000..75e34c65 --- /dev/null +++ b/src/andromede/simulation/strategy.py @@ -0,0 +1,82 @@ +# 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 abc import ABC, abstractmethod +from typing import Generator, Optional + +from andromede.expression import ExpressionNode +from andromede.model import Constraint, Model, ProblemContext, Variable + + +class ModelSelectionStrategy(ABC): + """ + Abstract class to specify the strategy of the created problem. + Its derived classes select variables and constraints for the optimization problems: + - InvestmentProblemStrategy: Keep investment and coupling variables and constraints only for a BendersDecomposed master + - OperationalProblemStrategy: Keep operational and coupling variables and constraints only for a BendersDecomposed sub-problems + - MergedProblemStrategy: Keep all variables and constraints + """ + + def get_variables(self, model: Model) -> Generator[Variable, None, None]: + for variable in model.variables.values(): + if self._keep_from_context(variable.context): + yield variable + + def get_constraints(self, model: Model) -> Generator[Constraint, None, None]: + for constraint in model.get_all_constraints(): + if self._keep_from_context(constraint.context): + yield constraint + + @abstractmethod + def _keep_from_context(self, context: ProblemContext) -> bool: + ... + + @abstractmethod + def get_objectives( + self, model: Model + ) -> Generator[Optional[ExpressionNode], None, None]: + ... + + +class MergedProblemStrategy(ModelSelectionStrategy): + def _keep_from_context(self, context: ProblemContext) -> bool: + return True + + def get_objectives( + self, model: Model + ) -> Generator[Optional[ExpressionNode], None, None]: + yield model.objective_operational_contribution + yield model.objective_investment_contribution + + +class InvestmentProblemStrategy(ModelSelectionStrategy): + def _keep_from_context(self, context: ProblemContext) -> bool: + return ( + context == ProblemContext.INVESTMENT or context == ProblemContext.COUPLING + ) + + def get_objectives( + self, model: Model + ) -> Generator[Optional[ExpressionNode], None, None]: + yield model.objective_investment_contribution + + +class OperationalProblemStrategy(ModelSelectionStrategy): + def _keep_from_context(self, context: ProblemContext) -> bool: + return ( + context == ProblemContext.OPERATIONAL or context == ProblemContext.COUPLING + ) + + def get_objectives( + self, model: Model + ) -> Generator[Optional[ExpressionNode], None, None]: + yield model.objective_operational_contribution diff --git a/tests/andromede/test_xpansion.py b/tests/andromede/test_xpansion.py index d34eb744..6e37faf3 100644 --- a/tests/andromede/test_xpansion.py +++ b/tests/andromede/test_xpansion.py @@ -32,12 +32,9 @@ int_variable, model, ) -from andromede.model.model import ( - MergedProblemStrategy, - PortFieldDefinition, - PortFieldId, -) +from andromede.model.model import PortFieldDefinition, PortFieldId from andromede.simulation import ( + MergedProblemStrategy, OutputValues, TimeBlock, build_benders_decomposed_problem, @@ -224,7 +221,7 @@ def test_generation_xpansion_single_time_step_single_scenario( database, TimeBlock(1, [0]), scenarios, - problem_strategy=MergedProblemStrategy, + problem_strategy=MergedProblemStrategy(), ) status = problem.solver.Solve()