Skip to content

Feature/network cloning #28

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

Merged
merged 14 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
4 changes: 4 additions & 0 deletions src/andromede/expression/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ def literal(value: float) -> LiteralNode:
return LiteralNode(value)


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


@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"),
)
51 changes: 19 additions & 32 deletions src/andromede/model/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@
#
# 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_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 +35,39 @@ 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):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think with the definition of is_unbound, I am allowed to use +inf in both lower bound and upper bound without raising an exception (idem with -inf). I think the check should be tighter ie. self.lower_bound != -inf and self.upper_bound != +inf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the ComparisonNode part, it won't be a problem because some lines below we set the bounds ourselves.
However, for LiteralNodes and ParameterNodes (else statement), I agree with you. I will fix it

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 upper_bound is not None:
self.upper_bound = upper_bound
else:
self.upper_bound = literal(float("inf"))
def replicate(self, /, **changes: Any) -> "Constraint":
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the backslash useful ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of code, no. It's for the developers.
It's a pythonic way of saying that there are no positional arguments allowed, only keyword arguments

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
Loading