Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
d2b46d2
Add first tests on thermal heuristics
May 28, 2024
24700f4
Implement accurate heuristic on simple problem
May 28, 2024
fd3a884
Add comments
May 28, 2024
0a6d4df
Add fast heuristic and improve accurate
May 31, 2024
c703040
rename test heuristic
May 31, 2024
9db4b54
Implement complex model
May 31, 2024
83360de
Accurate for complex model
May 31, 2024
bde9904
Type errors and useless solver parameters
Jun 4, 2024
4bfd696
check hourly outputs
Jun 4, 2024
3427eb4
fast heuristic for complex case
Jun 4, 2024
c79ba8f
Test for 2 weeks
Jun 5, 2024
dd61b8d
Test for 2 scenarios
Jun 6, 2024
5cb88db
add test for scenarios
Jun 7, 2024
9a9ec8d
Add more details on tests concerning scenarios
Jun 10, 2024
1be8483
Add milp test for second case
Jun 12, 2024
96fa166
remove milp test
Jun 13, 2024
add82e9
Add test accurate for second test case
Jun 13, 2024
40282a0
Add fast test for second test case
Jun 13, 2024
5700856
convert fast to optimization problem
Jun 5, 2024
dcd2c47
Optimization problem for fast on simple case
Jun 17, 2024
ea55b7d
Optimization model fast complex case
Jun 18, 2024
25bddc0
Small corrections
Jun 18, 2024
db17284
Correct test
Jun 18, 2024
6cf6611
Optimization problem heuristic fast second complex test case
Jun 18, 2024
3d9cb5b
Equivalent solution for fast heuristic optimization
Jun 21, 2024
57e5f7c
Api for fast and accurate models
Jun 25, 2024
e828f65
Api for accurate heuristic model
Jun 25, 2024
78a4712
Move models to library
Jun 26, 2024
0b10902
Move some functions from tests to src
Jun 27, 2024
912f083
Remove all functions from simple test
Jun 27, 2024
5b5c2b0
Move functions from second complex test to src
Jun 27, 2024
412e62c
Remove functions from second test and filter ts on scenarios and time…
Jun 28, 2024
e4fc78a
Sort imports
Jun 28, 2024
282c0f3
Run black
Jun 28, 2024
547ba3c
Change solver to fix tests in the ci
Jun 28, 2024
3f3d09f
Fix ci
Jun 28, 2024
6061ffe
Rename tests
Jun 28, 2024
e7c4cbe
Generalize check_output
Jul 2, 2024
8067cc8
More tests on data
Jul 2, 2024
c1408fa
Refactor model
Jul 3, 2024
17c76e6
Refactor problem.py
Jul 3, 2024
0ad6401
Generic database
Jul 3, 2024
54c93c0
Correct error
Jul 3, 2024
585fc50
Create ThermalProblemBuilder
Jul 4, 2024
ec850d8
Resolution steps
Jul 4, 2024
a3325e7
Simplify get_value out of database
Jul 4, 2024
5fadf5d
Simplify update of database
Jul 8, 2024
f259b55
Fix ci
Jul 8, 2024
f59285c
Remove not used imports
Jul 8, 2024
b6ec938
New test
Jul 15, 2024
38aafbb
Small changes
Jul 15, 2024
627aeb2
New test with bc
Jul 15, 2024
a5dcb1f
Refactoring
Jul 30, 2024
2915a19
Exhibit models
Aug 1, 2024
8663444
Remove unused imports
Aug 2, 2024
945c58d
New class for time scenario parameters
Aug 2, 2024
8a42060
Refactor solve
Aug 2, 2024
c88263c
Sort imports
Aug 2, 2024
3fbfa5d
Formating
Aug 2, 2024
8a0fe98
Refactor data_path
Aug 2, 2024
dec3bd5
Fix test
Aug 2, 2024
8ed1d47
Refactor models
Aug 2, 2024
042fc12
Fix test
Aug 2, 2024
e099b75
Run isort
Aug 2, 2024
f1f08e8
Week scenario parameters
Aug 2, 2024
d5f1535
Refactor solve
Aug 2, 2024
296f532
Rename functions
Aug 5, 2024
cb14787
Refactor cluster parameters
Aug 5, 2024
1d2f622
Correct error
Aug 5, 2024
c2abe5a
Refactor update
Aug 5, 2024
9a4ddbe
New test
Aug 7, 2024
75df4fd
New test with ramp
Aug 8, 2024
1d0da2c
Refactor
Aug 9, 2024
d2334fe
Add description for tests
Aug 9, 2024
137dc77
Beginning of new test
Aug 20, 2024
0329ffa
Improve edit_value
Aug 20, 2024
4b7bb35
Improve cluster parameters
Aug 20, 2024
d9831be
Move expected_output class
Aug 20, 2024
270200b
Change names
Aug 20, 2024
bb36d38
Remove network and database building from thermal_problem_builder
Aug 20, 2024
3c694b4
Correct pytest fixture
Aug 20, 2024
580ddcc
Remove solve from thermal problem builder
Aug 20, 2024
2c9a765
Fix test
Aug 20, 2024
1aebb07
Move tests
Aug 20, 2024
9ce1fd5
Improve test with parameters
Aug 21, 2024
5a57bcb
Use data_path
Aug 21, 2024
b4790ce
Improve pytest fixture
Aug 21, 2024
35a2f64
Improve tests
Aug 21, 2024
a34780f
Fix ci
Aug 26, 2024
a4534e7
Fix tests
Aug 26, 2024
b039478
Fix test on day ahead reserve
Aug 29, 2024
9a68f0e
Unused code
Sep 6, 2024
f9f5134
api file
Oct 24, 2024
2cf0f5a
Small changes
Nov 4, 2024
38c4535
Merge branch 'main' into api_thermal_heuristic
Juliette-Gerbaux Jun 17, 2026
a8271fb
Move files
Juliette-Gerbaux Jun 17, 2026
2015411
Prepare test_one_cluster
Juliette-Gerbaux Jun 17, 2026
81fb135
Update tests
Juliette-Gerbaux Jun 17, 2026
e50a82f
Update test with bc
Juliette-Gerbaux Jun 17, 2026
05e3791
Update test with different scenarios and xpress settings
Juliette-Gerbaux Jun 19, 2026
2622caa
Remove test with different scenarios and xpress settings
Juliette-Gerbaux Jun 19, 2026
cb853e4
Remove day ahead test
Juliette-Gerbaux Jun 19, 2026
85bf988
Implement fast heuristic as an algorithm
Juliette-Gerbaux Jun 19, 2026
46b3805
Implement accurate heuristic
Juliette-Gerbaux Jun 19, 2026
54b9fed
integer-strategy in system and heuristic in optim-config
Juliette-Gerbaux Jun 26, 2026
11652f3
Implement interger-strategy in variables building
Juliette-Gerbaux Jun 26, 2026
19a0128
heuristic id in parsing
Juliette-Gerbaux Jul 13, 2026
7eb031c
Add heuristic validation and update optim-config documentation
Juliette-Gerbaux Jul 13, 2026
f81b7a9
Workflow with 2 iterations and thermal heuristic
Juliette-Gerbaux Jul 13, 2026
bd43257
Remove obsolete code
Juliette-Gerbaux Jul 13, 2026
ff9533d
Fix ci
Juliette-Gerbaux Jul 13, 2026
db5ef38
Test with ramp
Juliette-Gerbaux Jul 13, 2026
1811dff
Validate heuristic input/output time-dependence at optim-config parsing
Juliette-Gerbaux Jul 24, 2026
e2f88ec
Validate optim-config automatically in SimulationSession
Juliette-Gerbaux Jul 24, 2026
15456c0
Merge branch 'main' into api_thermal_heuristic
Juliette-Gerbaux Jul 24, 2026
decc740
Fix ci
Juliette-Gerbaux Jul 24, 2026
3320444
Refactor e2e tests
Juliette-Gerbaux Jul 24, 2026
d45ce1a
Formatting
tbittar Jul 30, 2026
bcecb33
Refactoring
tbittar Jul 31, 2026
b96b782
Fix solution retrieval, window size in fast heuristic bugs
tbittar Jul 31, 2026
f0ac336
Review comments
Juliette-Gerbaux Aug 5, 2026
310f1a3
non_prop_cost in tests
Juliette-Gerbaux Aug 5, 2026
fb21ca4
Remove tests of heuristic behaviour
Juliette-Gerbaux Aug 7, 2026
f159263
New mixed strategies test
Juliette-Gerbaux Aug 7, 2026
9d0ab1d
Enforce heuristic-id consistency with integer-strategy
Juliette-Gerbaux Aug 7, 2026
f85f8cc
Guard bound mutation on merged relaxed/exact variables
Juliette-Gerbaux Aug 7, 2026
8155f8e
Document integer-strategy and thermal heuristics
Juliette-Gerbaux Aug 10, 2026
194932e
Merge branch 'main' into api_thermal_heuristic
Juliette-Gerbaux Aug 10, 2026
faab029
Update changelog
Juliette-Gerbaux Aug 10, 2026
b4d2f3f
Add lower_bound()/upper_bound() expression operators
Juliette-Gerbaux Aug 10, 2026
4a484e8
Merge branch 'main' into feat/lb_ub_operator
Juliette-Gerbaux Aug 11, 2026
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
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ All notable changes to GemsPy are documented here.
compute tighter variable bounds from the first solve. Each model declares
what a heuristic reads/writes via `models[].heuristics` in
`optim-config.yml`.
- **`lower_bound(variable_name)`** and **`upper_bound(variable_name)`** operators in the
expression language, usable in `extra-outputs` and port-field-definitions. Both take a bare
variable identifier and return its *current* lower/upper bound post-solve — in particular
reflecting mutations made by thermal heuristics (see "Integer strategy and thermal
heuristics" above), which previously had no way to be surfaced in results. Validated at
model-build time; using them inside constraints, binding-constraints, objective
contributions, or variable bounds raises a `ValueError`.

## [0.1.3] - 2026-07-24

Expand Down
2 changes: 1 addition & 1 deletion docs/agents/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ nature (they build a study with `gems_craft` and solve it with `gems_runner` in
| Unit — gems_craft / system | `tests/unittests/gems_craft/system/` | Model, network, and port object behaviour |
| Unit — gems_craft / system parsing | `tests/unittests/gems_craft/system_parsing/` | System YAML parsing |
| Unit — gems_craft_hybrid | `tests/unittests/gems_craft_hybrid/` | Hybrid GEMS/Antares Simulator schema parsing |
| Unit — gems_runner / expression | `tests/unittests/gems_runner/expression/` | Solver-output expression evaluation (`dual()`, `reduced_cost()`, `variable()`) |
| Unit — gems_runner / expression | `tests/unittests/gems_runner/expression/` | Solver-output expression evaluation (`dual()`, `reduced_cost()`, `lower_bound()`, `upper_bound()`, `variable()`) |
| Unit — gems_runner / simulation | `tests/unittests/gems_runner/simulation/` | Full problem build + solve on small networks |
| End-to-end — functional | `tests/e2e/functional/` | Cross-cutting tests: library/system combinations, stochastic, investment, scenario builder |
| End-to-end — models | `tests/e2e/models/` | Model-level tests (andromede-v1 models, operator tests, proof-of-concept models) |
Expand Down
8 changes: 8 additions & 0 deletions src/gems_craft/expression/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ExpressionNode,
FloorNode,
LiteralNode,
LowerBoundNode,
MaxNode,
MinNode,
ParameterNode,
Expand All @@ -34,6 +35,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
VariableNode,
)
from .visitor import ExpressionVisitorOperations, visit
Expand Down Expand Up @@ -111,6 +113,12 @@ def dual(self, node: DualNode) -> ExpressionNode:
def reduced_cost(self, node: ReducedCostNode) -> ExpressionNode:
return ReducedCostNode(node.variable_id)

def lower_bound(self, node: LowerBoundNode) -> ExpressionNode:
return LowerBoundNode(node.variable_id)

def upper_bound(self, node: UpperBoundNode) -> ExpressionNode:
return UpperBoundNode(node.variable_id)


def copy_expression(expression: ExpressionNode) -> ExpressionNode:
return visit(expression, CopyVisitor())
8 changes: 8 additions & 0 deletions src/gems_craft/expression/degree.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
CeilNode,
DualNode,
FloorNode,
LowerBoundNode,
MaxNode,
MinNode,
PortFieldAggregatorNode,
Expand All @@ -29,6 +30,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
)

from .expression import (
Expand Down Expand Up @@ -132,6 +134,12 @@ def dual(self, node: DualNode) -> int | float:
def reduced_cost(self, node: ReducedCostNode) -> int | float:
return math.inf

def lower_bound(self, node: LowerBoundNode) -> int | float:
return math.inf

def upper_bound(self, node: UpperBoundNode) -> int | float:
return math.inf


def compute_degree(expression: ExpressionNode) -> int | float:
return visit(expression, ExpressionDegreeVisitor())
Expand Down
12 changes: 12 additions & 0 deletions src/gems_craft/expression/equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
CeilNode,
DualNode,
FloorNode,
LowerBoundNode,
MaxNode,
MinNode,
PortFieldAggregatorNode,
Expand All @@ -42,6 +43,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
)


Expand Down Expand Up @@ -115,6 +117,10 @@ def visit(self, left: ExpressionNode, right: ExpressionNode) -> bool:
return self.dual(left, right)
if isinstance(left, ReducedCostNode) and isinstance(right, ReducedCostNode):
return self.reduced_cost(left, right)
if isinstance(left, LowerBoundNode) and isinstance(right, LowerBoundNode):
return self.lower_bound(left, right)
if isinstance(left, UpperBoundNode) and isinstance(right, UpperBoundNode):
return self.upper_bound(left, right)
raise NotImplementedError(f"Equality not implemented for {left.__class__}")

def literal(self, left: LiteralNode, right: LiteralNode) -> bool:
Expand Down Expand Up @@ -217,6 +223,12 @@ def dual(self, left: DualNode, right: DualNode) -> bool:
def reduced_cost(self, left: ReducedCostNode, right: ReducedCostNode) -> bool:
return left.variable_id == right.variable_id

def lower_bound(self, left: LowerBoundNode, right: LowerBoundNode) -> bool:
return left.variable_id == right.variable_id

def upper_bound(self, left: UpperBoundNode, right: UpperBoundNode) -> bool:
return left.variable_id == right.variable_id


def expressions_equal(
left: ExpressionNode, right: ExpressionNode, abs_tol: float = 0, rel_tol: float = 0
Expand Down
10 changes: 10 additions & 0 deletions src/gems_craft/expression/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,16 @@ class ReducedCostNode(ExpressionNode):
variable_id: str


@dataclass(frozen=True, eq=False)
class LowerBoundNode(ExpressionNode):
variable_id: str


@dataclass(frozen=True, eq=False)
class UpperBoundNode(ExpressionNode):
variable_id: str


def sum_expressions(expressions: Sequence[ExpressionNode]) -> ExpressionNode:
if len(expressions) == 0:
return LiteralNode(0)
Expand Down
8 changes: 8 additions & 0 deletions src/gems_craft/expression/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ExpressionNode,
FloorNode,
LiteralNode,
LowerBoundNode,
MaxNode,
MinNode,
MultiplicationNode,
Expand All @@ -40,6 +41,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
VariableNode,
)
from .visitor import ExpressionVisitor, T, visit
Expand Down Expand Up @@ -156,6 +158,12 @@ def dual(self, node: DualNode) -> IndexingStructure:
def reduced_cost(self, node: ReducedCostNode) -> IndexingStructure:
return self.context.get_variable_structure(node.variable_id)

def lower_bound(self, node: LowerBoundNode) -> IndexingStructure:
return self.context.get_variable_structure(node.variable_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That's a bit a detail, but do you think the lower/upper bounds always have the same indexing structure as their related variable? I would say that this is not necessarily the case (ex: variable time-dep but bounds time-indep).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree, and more precisely, I think a lower/upper bound should always be "less granularly" indexed than the variable (the remark is valid for both time and scenario). Ie. the var can depend on time/scenario but not the bound. The reverse should not be possible (at least you think about a use case where we need that ?)

And if you agree on that, no checks are currently made within GemsPy, which may lead to some failures, see #267

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Then here, I think we should enrich the IdexingStructureProvider to also bear information about variable bounds indexing (clean way)

Otherwise simply using the variable indexing should be fine at the cost of possibly having some expressions with a constant timeseries (should work but not optimal)

@Juliette-Gerbaux Juliette-Gerbaux Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought it was better to have the same indexing structure as the variable: since the heuristic can change the value of the bound and make it time-dependent, it seemed more intuitive for the heuristic to change only the value and not the indexing structure — but I'm open to changing that. The issue #267 would still be valid.


def upper_bound(self, node: UpperBoundNode) -> IndexingStructure:
return self.context.get_variable_structure(node.variable_id)


def compute_indexation(
expression: ExpressionNode, provider: IndexingStructureProvider
Expand Down
22 changes: 22 additions & 0 deletions src/gems_craft/expression/parsing/parse_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
Comparator,
ComparisonNode,
DualNode,
LowerBoundNode,
PortFieldAggregatorNode,
PortFieldNode,
ReducedCostNode,
UpperBoundNode,
maximum,
minimum,
)
Expand Down Expand Up @@ -194,6 +196,22 @@ def _visit_reduced_cost(self, arg_exprs: list) -> ExpressionNode:
raise ValueError(f"'{vid}' is not a variable of the model.")
return ReducedCostNode(vid)

def _visit_lower_bound(self, arg_exprs: list) -> ExpressionNode:
if len(arg_exprs) != 1:
raise ValueError("lower_bound() requires exactly 1 argument.")
vid = arg_exprs[0].getText() # type: ignore
if vid not in self.identifiers.variables:
raise ValueError(f"'{vid}' is not a variable of the model.")
return LowerBoundNode(vid)

def _visit_upper_bound(self, arg_exprs: list) -> ExpressionNode:
if len(arg_exprs) != 1:
raise ValueError("upper_bound() requires exactly 1 argument.")
vid = arg_exprs[0].getText() # type: ignore
if vid not in self.identifiers.variables:
raise ValueError(f"'{vid}' is not a variable of the model.")
return UpperBoundNode(vid)

# Visit a parse tree produced by ExprParser#function.
def visitFunction(self, ctx: ExprParser.FunctionContext) -> ExpressionNode:
function_name: str = ctx.IDENTIFIER().getText() # type: ignore
Expand All @@ -204,6 +222,10 @@ def visitFunction(self, ctx: ExprParser.FunctionContext) -> ExpressionNode:
return self._visit_dual(arg_exprs)
if function_name == "reduced_cost":
return self._visit_reduced_cost(arg_exprs)
if function_name == "lower_bound":
return self._visit_lower_bound(arg_exprs)
if function_name == "upper_bound":
return self._visit_upper_bound(arg_exprs)

args: list[ExpressionNode] = (
[expr.accept(self) for expr in arg_exprs] # type: ignore
Expand Down
8 changes: 8 additions & 0 deletions src/gems_craft/expression/print.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
DualNode,
ExpressionNode,
FloorNode,
LowerBoundNode,
MaxNode,
MinNode,
PortFieldAggregatorNode,
Expand All @@ -29,6 +30,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
)

from .expression import (
Expand Down Expand Up @@ -144,6 +146,12 @@ def dual(self, node: DualNode) -> str:
def reduced_cost(self, node: ReducedCostNode) -> str:
return f"reduced_cost({node.variable_id})"

def lower_bound(self, node: LowerBoundNode) -> str:
return f"lower_bound({node.variable_id})"

def upper_bound(self, node: UpperBoundNode) -> str:
return f"upper_bound({node.variable_id})"


def print_expr(expression: ExpressionNode) -> str:
return visit(expression, PrinterVisitor())
8 changes: 8 additions & 0 deletions src/gems_craft/expression/uses_sum_connections_on.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ExpressionNode,
FloorNode,
LiteralNode,
LowerBoundNode,
MaxNode,
MinNode,
MultiplicationNode,
Expand All @@ -34,6 +35,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
VariableNode,
)

Expand Down Expand Up @@ -122,6 +124,12 @@ def dual(self, node: DualNode) -> bool:
def reduced_cost(self, node: ReducedCostNode) -> bool:
return False

def lower_bound(self, node: LowerBoundNode) -> bool:
return False

def upper_bound(self, node: UpperBoundNode) -> bool:
return False


def uses_sum_connections_on(
expr: ExpressionNode, port_name: str, field_name: str
Expand Down
12 changes: 12 additions & 0 deletions src/gems_craft/expression/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ExpressionNode,
FloorNode,
LiteralNode,
LowerBoundNode,
MaxNode,
MinNode,
MultiplicationNode,
Expand All @@ -42,6 +43,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
VariableNode,
)

Expand Down Expand Up @@ -126,6 +128,12 @@ def dual(self, node: DualNode) -> T: ...
@abstractmethod
def reduced_cost(self, node: ReducedCostNode) -> T: ...

@abstractmethod
def lower_bound(self, node: LowerBoundNode) -> T: ...

@abstractmethod
def upper_bound(self, node: UpperBoundNode) -> T: ...


def visit(root: ExpressionNode, visitor: ExpressionVisitor[T]) -> T:
"""
Expand Down Expand Up @@ -177,6 +185,10 @@ def visit(root: ExpressionNode, visitor: ExpressionVisitor[T]) -> T:
return visitor.dual(root)
elif isinstance(root, ReducedCostNode):
return visitor.reduced_cost(root)
elif isinstance(root, LowerBoundNode):
return visitor.lower_bound(root)
elif isinstance(root, UpperBoundNode):
return visitor.upper_bound(root)
raise ValueError(f"Unknown expression node type {root.__class__}")


Expand Down
8 changes: 8 additions & 0 deletions src/gems_craft/model/port.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
CeilNode,
DualNode,
FloorNode,
LowerBoundNode,
MaxNode,
MinNode,
PortFieldAggregatorNode,
Expand All @@ -42,6 +43,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
)
from gems_craft.expression.visitor import visit

Expand Down Expand Up @@ -178,6 +180,12 @@ def dual(self, node: DualNode) -> None:
def reduced_cost(self, node: ReducedCostNode) -> None:
pass # reduced_cost() is permitted in port-field definitions

def lower_bound(self, node: LowerBoundNode) -> None:
pass # lower_bound() is permitted in port-field definitions

def upper_bound(self, node: UpperBoundNode) -> None:
pass # upper_bound() is permitted in port-field definitions


def _validate_port_field_expression(definition: PortFieldDefinition) -> None:
visit(definition.definition, _PortFieldExpressionChecker())
8 changes: 8 additions & 0 deletions src/gems_craft/model/resolve_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
DualNode,
FloorNode,
LiteralNode,
LowerBoundNode,
MaxNode,
MinNode,
MultiplicationNode,
Expand All @@ -36,6 +37,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
VariableNode,
)
from gems_craft.expression.indexing_structure import IndexingStructure
Expand Down Expand Up @@ -291,6 +293,12 @@ def dual(self, node: DualNode) -> None:
def reduced_cost(self, node: ReducedCostNode) -> None:
pass

def lower_bound(self, node: LowerBoundNode) -> None:
pass

def upper_bound(self, node: UpperBoundNode) -> None:
pass


def _forbid_bare_port_field(expr: ExpressionNode, context: str) -> None:
visit(expr, _ForbidBarePortFieldVisitor(context))
Expand Down
8 changes: 8 additions & 0 deletions src/gems_runner/expression/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
ExpressionNode,
FloorNode,
LiteralNode,
LowerBoundNode,
MaxNode,
MinNode,
ParameterNode,
Expand All @@ -35,6 +36,7 @@
TimeEvalNode,
TimeShiftNode,
TimeSumNode,
UpperBoundNode,
VariableNode,
)
from gems_craft.expression.indexing import IndexingStructureProvider
Expand Down Expand Up @@ -139,6 +141,12 @@ def dual(self, node: DualNode) -> float:
def reduced_cost(self, node: ReducedCostNode) -> float:
raise NotImplementedError("reduced_cost() is not statically evaluable.")

def lower_bound(self, node: LowerBoundNode) -> float:
raise NotImplementedError("lower_bound() is not statically evaluable.")

def upper_bound(self, node: UpperBoundNode) -> float:
raise NotImplementedError("upper_bound() is not statically evaluable.")


def evaluate(expression: ExpressionNode, value_provider: ValueProvider) -> float:
return visit(expression, EvaluationVisitor(value_provider))
Expand Down
Loading
Loading