Skip to content

Commit

Permalink
Feature/network cloning (#28)
Browse files Browse the repository at this point in the history
* Move pathway tests to functional folder

* Add replicate method to classes to be able to copy them to tree

* Remove integration test from functional xpansion test

* Modify DecisionTree

* Move decision_tree test to unit tests folder

* Ideas for the coupling_model

* Write subproblem with tree

* Add first pathway test

* Add three node test for pathway

* Add prob to tree nodes for expectation computation

* Remove test debug init

* Add prob to decision tree node

* Add ExpectedValueStrategy to Benders

* Retour of PR comments
  • Loading branch information
ianmnz authored Jul 22, 2024
1 parent 303bd14 commit 964297a
Show file tree
Hide file tree
Showing 20 changed files with 1,049 additions and 665 deletions.
8 changes: 8 additions & 0 deletions src/andromede/expression/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ def literal(value: float) -> LiteralNode:
return LiteralNode(value)


def is_unbound(expr: ExpressionNode) -> bool:
return isinstance(expr, LiteralNode) and (abs(expr.value) == float("inf"))


def is_non_negative(expr: ExpressionNode) -> bool:
return isinstance(expr, LiteralNode) and (expr.value >= 0)


@dataclass(frozen=True, eq=False)
class UnaryOperatorNode(ExpressionNode):
operand: ExpressionNode
Expand Down
10 changes: 8 additions & 2 deletions src/andromede/libs/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,10 +441,16 @@
float_variable(
"invested_capa",
lower_bound=literal(0),
upper_bound=param("max_invest"),
structure=CONSTANT,
context=ProblemContext.COUPLING,
),
float_variable(
"delta_invest",
lower_bound=literal(0),
upper_bound=param("max_invest"),
structure=CONSTANT,
context=ProblemContext.INVESTMENT,
),
],
ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")],
port_fields_definitions=[
Expand All @@ -463,5 +469,5 @@
objective_operational_contribution=(param("op_cost") * var("generation"))
.sum()
.expec(),
objective_investment_contribution=param("invest_cost") * var("invested_capa"),
objective_investment_contribution=param("invest_cost") * var("delta_invest"),
)
56 changes: 25 additions & 31 deletions src/andromede/model/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@
#
# This file is part of the Antares project.

from typing import Optional
from dataclasses import dataclass, field, replace
from typing import Any

from andromede.expression.degree import is_constant
from andromede.expression.expression import (
Comparator,
ComparisonNode,
ExpressionNode,
is_non_negative,
is_unbound,
literal,
)
from andromede.expression.print import print_expr
from andromede.model.common import ProblemContext


@dataclass
class Constraint:
"""
A constraint linking variables and parameters of a model together.
Expand All @@ -32,55 +36,45 @@ class Constraint:

name: str
expression: ExpressionNode
lower_bound: ExpressionNode
upper_bound: ExpressionNode
context: ProblemContext
lower_bound: ExpressionNode = field(default=literal(-float("inf")))
upper_bound: ExpressionNode = field(default=literal(float("inf")))
context: ProblemContext = field(default=ProblemContext.OPERATIONAL)

def __init__(
def __post_init__(
self,
name: str,
expression: ExpressionNode,
lower_bound: Optional[ExpressionNode] = None,
upper_bound: Optional[ExpressionNode] = None,
context: ProblemContext = ProblemContext.OPERATIONAL,
) -> None:
self.name = name
self.context = context

if isinstance(expression, ComparisonNode):
if lower_bound is not None or upper_bound is not None:
if isinstance(self.expression, ComparisonNode):
if not is_unbound(self.lower_bound) or not is_unbound(self.upper_bound):
raise ValueError(
"Both comparison between two expressions and a bound are specfied, set either only a comparison between expressions or a single linear expression with bounds."
)

merged_expr = expression.left - expression.right
self.expression = merged_expr

if expression.comparator == Comparator.LESS_THAN:
if self.expression.comparator == Comparator.LESS_THAN:
# lhs - rhs <= 0
self.upper_bound = literal(0)
self.lower_bound = literal(-float("inf"))
elif expression.comparator == Comparator.GREATER_THAN:
elif self.expression.comparator == Comparator.GREATER_THAN:
# lhs - rhs >= 0
self.lower_bound = literal(0)
self.upper_bound = literal(float("inf"))
else: # lhs - rhs == 0
self.lower_bound = literal(0)
self.upper_bound = literal(0)

self.expression = self.expression.left - self.expression.right

else:
for bound in [lower_bound, upper_bound]:
if bound is not None and not is_constant(bound):
for bound in [self.lower_bound, self.upper_bound]:
if not is_constant(bound):
raise ValueError(
f"The bounds of a constraint should not contain variables, {print_expr(bound)} was given."
)

self.expression = expression
if lower_bound is not None:
self.lower_bound = lower_bound
else:
self.lower_bound = literal(-float("inf"))
if is_unbound(self.lower_bound) and is_non_negative(self.lower_bound):
raise ValueError("Lower bound should not be +Inf")

if upper_bound is not None:
self.upper_bound = upper_bound
else:
self.upper_bound = literal(float("inf"))
if is_unbound(self.upper_bound) and not is_non_negative(self.upper_bound):
raise ValueError("Upper bound should not be -Inf")

def replicate(self, /, **changes: Any) -> "Constraint":
return replace(self, **changes)
20 changes: 15 additions & 5 deletions src/andromede/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@
defining parameters, variables, and equations.
"""
import itertools
from dataclasses import dataclass, field
from typing import Dict, Iterable, Optional

from anytree import LevelOrderIter
from anytree import Node as TreeNode
from dataclasses import dataclass, field, replace
from typing import Any, Dict, Iterable, Optional

from andromede.expression import (
AdditionNode,
Expand Down Expand Up @@ -110,12 +107,18 @@ class ModelPort:
port_type: PortType
port_name: str

def replicate(self, /, **changes: Any) -> "ModelPort":
return replace(self, **changes)


@dataclass(frozen=True)
class PortFieldId:
port_name: str
field_name: str

def replicate(self, /, **changes: Any) -> "PortFieldId":
return replace(self, **changes)


@dataclass(frozen=True)
class PortFieldDefinition:
Expand All @@ -129,6 +132,9 @@ class PortFieldDefinition:
def __post_init__(self) -> None:
_validate_port_field_expression(self)

def replicate(self, /, **changes: Any) -> "PortFieldDefinition":
return replace(self, **changes)


def port_field_def(
port_name: str, field_name: str, definition: ExpressionNode
Expand Down Expand Up @@ -186,6 +192,10 @@ def get_all_constraints(self) -> Iterable[Constraint]:
self.binding_constraints.values(), self.constraints.values()
)

def replicate(self, /, **changes: Any) -> "Model":
# Shallow copy
return replace(self, **changes)


def model(
id: str,
Expand Down
6 changes: 5 additions & 1 deletion src/andromede/model/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
#
# This file is part of the Antares project.

from dataclasses import dataclass
from dataclasses import dataclass, replace
from typing import Any

from andromede.expression.indexing_structure import IndexingStructure
from andromede.model.common import ValueType
Expand All @@ -28,6 +29,9 @@ class Parameter:
type: ValueType
structure: IndexingStructure

def replicate(self, /, **changes: Any) -> "Parameter":
return replace(self, **changes)


def int_parameter(
name: str,
Expand Down
7 changes: 5 additions & 2 deletions src/andromede/model/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
#
# This file is part of the Antares project.

from dataclasses import dataclass
from typing import Optional
from dataclasses import dataclass, replace
from typing import Any, Optional

from andromede.expression import ExpressionNode
from andromede.expression.degree import is_constant
Expand All @@ -38,6 +38,9 @@ def __post_init__(self) -> None:
if self.upper_bound and not is_constant(self.upper_bound):
raise ValueError("Upper bounds of variables must be constant")

def replicate(self, /, **changes: Any) -> "Variable":
return replace(self, **changes)


def int_variable(
name: str,
Expand Down
1 change: 1 addition & 0 deletions src/andromede/simulation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
BendersDecomposedProblem,
build_benders_decomposed_problem,
)
from .decision_tree import DecisionTreeNode, InterDecisionTimeScenarioConfig
from .optimization import BlockBorderManagement, OptimizationProblem, build_problem
from .output_values import BendersSolution, OutputValues
from .runner import BendersRunner, MergeMPSRunner
Expand Down
Loading

0 comments on commit 964297a

Please sign in to comment.