Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
58c12f8
WIP
tbittar Jun 21, 2024
0f285c6
Operator simplification on construction
tbittar Jun 21, 2024
ef9c8b6
Improve common operations on linear expr
tbittar Jun 25, 2024
4f0542c
Handle constraints
tbittar Jun 25, 2024
946c366
Equality comparator for linear expression
tbittar Jun 26, 2024
e1ebb29
WIP
tbittar Jun 26, 2024
10c2066
Sum shift
tbittar Jun 27, 2024
d13a649
WIP
tbittar Jul 11, 2024
75a5881
Shift and eval implementation in progress
tbittar Jul 12, 2024
2860242
TimeShift hashing
tbittar Jul 12, 2024
b640b22
Implement shift, eval and time sum of linear expressions
tbittar Jul 15, 2024
5f2823f
Test sum of linear expressions
tbittar Jul 15, 2024
e90254e
Fix printing term tests
tbittar Jul 16, 2024
c5214eb
More online simplifications, start handling constraints and ports
tbittar Jul 16, 2024
9ca614a
Make param and literal return expression node to later handle Express…
tbittar Jul 22, 2024
3d03d86
Fix model test
tbittar Jul 22, 2024
3251495
Fix test imports and port definition validation
tbittar Jul 23, 2024
6f85db0
Design problem between time operator / expression node
tbittar Jul 23, 2024
13ea7de
Fix circular imports
tbittar Jul 25, 2024
d092b1b
Fix syntax
tbittar Jul 26, 2024
48c96ff
Resolve linear expression
tbittar Jul 26, 2024
9de9cae
Fix circular imports
tbittar Aug 14, 2024
3a4004c
Parameter evaluation visitor implemented
tbittar Aug 14, 2024
8007306
Be able to create variables
tbittar Aug 14, 2024
c03705c
Merge branch 'main' into feature/better_expression_linearization
tbittar Aug 16, 2024
1e1ac18
Test resolve coefficients, update test evaluation context
tbittar Aug 16, 2024
1be9d84
Improve resolve coefficient API
tbittar Aug 16, 2024
b532a41
Start resolve variables, separate optimization context from optimizat…
tbittar Aug 19, 2024
b429782
Set objective
tbittar Aug 19, 2024
bda088c
Fix shift distribution and add component context for time operators
tbittar Aug 20, 2024
2c723cd
Temporary API for single shift over ExpressionNodeEfficient
tbittar Aug 21, 2024
27f8ad8
Fix variable get structure
tbittar Aug 21, 2024
bcafb95
Fix expectation computation
tbittar Aug 21, 2024
b0197da
Feature/update yaml parsing (#51)
tbittar Aug 21, 2024
1911e29
Fix interaction resolve ports / add component context
tbittar Aug 21, 2024
a904d9d
Uniformize imports
tbittar Aug 21, 2024
8b7a244
Fix some type checking issues, remove useless code
tbittar Aug 21, 2024
85d1628
Remove useless commented code
tbittar Aug 21, 2024
b5c3328
Remove useless commented code
tbittar Aug 21, 2024
344ac65
Improve type checking for constraint
tbittar Aug 22, 2024
600d1bc
Improve type checking for port field def
tbittar Aug 22, 2024
d3317ef
Improve type checking for **kwargs
tbittar Aug 22, 2024
7c9d427
Type checking and reformatting
tbittar Aug 22, 2024
6022781
Remove useless code
tbittar Aug 23, 2024
93eda7e
Rename files
tbittar Aug 27, 2024
952bce4
Rename file
tbittar Aug 27, 2024
85f3953
Remove 'efficient' suffix
tbittar Aug 27, 2024
f50f57f
Rename test files
tbittar Aug 27, 2024
abd1eed
Remove useless comments
tbittar Aug 27, 2024
f6a03c6
Comment and reformatting
tbittar Aug 27, 2024
9431fe7
WIP for parsing
tbittar Aug 27, 2024
02f8a39
WIP for yaml parsing
tbittar Sep 5, 2024
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
Prev Previous commit
Next Next commit
Resolve linear expression
  • Loading branch information
tbittar committed Jul 26, 2024
commit 48c96ffce45cb0dc19a33a3d434840d4a9790b60
12 changes: 6 additions & 6 deletions src/andromede/expression/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ class ValueProvider(ABC):
Depending on the implementation, evaluation may require a component id or not.
"""

@abstractmethod
def get_variable_value(self, name: str) -> float:
...
# @abstractmethod
# def get_variable_value(self, name: str) -> float:
# ...

@abstractmethod
def get_parameter_value(self, name: str) -> float:
...

@abstractmethod
def get_component_variable_value(self, component_id: str, name: str) -> float:
...
# @abstractmethod
# def get_component_variable_value(self, component_id: str, name: str) -> float:
# ...

@abstractmethod
def get_component_parameter_value(self, component_id: str, name: str) -> float:
Expand Down
201 changes: 201 additions & 0 deletions src/andromede/expression/evaluate_parameters_efficient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# 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 dataclasses import dataclass, field
from typing import Dict, List

from andromede.expression.expression import VariableNode
from andromede.expression.expression_efficient import (
ComparisonNode,
ComponentParameterNode,
ExpressionNodeEfficient,
ExpressionRange,
InstancesTimeIndex,
LiteralNode,
ParameterNode,
PortFieldAggregatorNode,
PortFieldNode,
ScenarioOperatorName,
ScenarioOperatorNode,
TimeAggregatorName,
TimeAggregatorNode,
TimeOperatorName,
TimeOperatorNode,
)
from andromede.expression.linear_expression_efficient import RowIndex

from .visitor import ExpressionVisitor, ExpressionVisitorOperations, T, visit


@dataclass
class TimeScenarioIndices:
time_indices: List[int]
scenario_indices: List[int]


class ValueProvider(ABC):
"""
Implementations are in charge of mapping parameters and variables to their values.
Depending on the implementation, evaluation may require a component id or not.
"""

# @abstractmethod
# def get_variable_value(self, name: str) -> float: ...

@abstractmethod
def get_parameter_value(
self, name: str, time_scenarios_indices: TimeScenarioIndices
) -> List[float]: ...

# @abstractmethod
# def get_component_variable_value(self, component_id: str, name: str) -> float: ...

@abstractmethod
def get_component_parameter_value(
self, component_id: str, name: str, time_scenarios_indices: TimeScenarioIndices
) -> List[float]: ...

# TODO: Should this really be an abstract method ? Or maybe, only the Provider in _make_value_provider should implement it. And the context attribute in the InstancesIndexVisitor is a ValueProvider that implements the parameter_is_constant_over_time method. Maybe create a child class of ValueProvider like TimeValueProvider ?
@abstractmethod
def parameter_is_constant_over_time(self, name: str) -> bool: ...


@dataclass(frozen=True)
class ParameterEvaluationVisitor(ExpressionVisitorOperations[float]):
"""
Evaluates the expression with respect to the provided context
(variables and parameters values).
"""

context: ValueProvider
row_id: RowIndex # TODO to be included in ValueProvider ?
time_scenario_indices: TimeScenarioIndices

def literal(self, node: LiteralNode) -> float:
return [node.value]

# def comparison(self, node: ComparisonNode) -> float:
# raise ValueError("Cannot evaluate comparison operator.")

# def variable(self, node: VariableNode) -> float:
# return self.context.get_variable_value(node.name)

def parameter(self, node: ParameterNode) -> float:
return self.context.get_parameter_value(node.name, self.time_scenario_indices)

def comp_parameter(self, node: ComponentParameterNode) -> float:
return self.context.get_component_parameter_value(
node.component_id, node.name, self.time_scenario_indices
)

# def comp_variable(self, node: ComponentVariableNode) -> float:
# return self.context.get_component_variable_value(node.component_id, node.name)

def time_operator(self, node: TimeOperatorNode) -> float:
self.time_scenario_indices.time_indices = get_time_ids_from_instances_index(
node.instances_index, self.context
)
if node.name == TimeOperatorName.SHIFT:
self.time_scenario_indices.time_indices = [
self.row_id.time + op_id
for op_id in self.time_scenario_indices.time_indices
]
elif node.name != TimeOperatorName.EVALUATION:
return NotImplemented
return visit(node.operand, self)

def time_aggregator(self, node: TimeAggregatorNode) -> float:
if node.name in [TimeAggregatorName.SUM]:
# TODO: Where is the sum ?
return visit(node.operand, self)
else:
return NotImplemented

def scenario_operator(self, node: ScenarioOperatorNode) -> float:
if node.name in [ScenarioOperatorName.EXPECTATION]:
return visit(node.operand, self)
else:
return NotImplemented

def port_field(self, node: PortFieldNode) -> float:
raise ValueError("Port fields must be resolved before evaluating parameters")

def port_field_aggregator(self, node: PortFieldAggregatorNode) -> float:
raise ValueError("Port fields must be resolved before evaluating parameters")


def resolve_coefficient(
expression: ExpressionNodeEfficient, value_provider: ValueProvider, row_id: RowIndex
) -> float:
return visit(expression, ParameterEvaluationVisitor(value_provider, row_id))


@dataclass(frozen=True)
class InstancesIndexVisitor(ParameterEvaluationVisitor):
"""
Evaluates an expression given as instances index which should have no variable and constant parameter values.
"""

# def variable(self, node: VariableNode) -> float:
# raise ValueError("An instance index expression cannot contain variable")

def parameter(self, node: ParameterNode) -> float:
if not self.context.parameter_is_constant_over_time(node.name):
raise ValueError(
"Parameter given in an instance index expression must be constant over time"
)
return self.context.get_parameter_value(node.name)

def time_operator(self, node: TimeOperatorNode) -> float:
raise ValueError("An instance index expression cannot contain time operator")

def time_aggregator(self, node: TimeAggregatorNode) -> float:
raise ValueError("An instance index expression cannot contain time aggregator")


def float_to_int(value: float) -> int:
if isinstance(value, int) or value.is_integer():
return int(value)
else:
raise ValueError(f"{value} is not an integer.")


def evaluate_time_id(
expr: ExpressionNodeEfficient, value_provider: ValueProvider
) -> int:
float_time_id = visit(expr, InstancesIndexVisitor(value_provider))
try:
time_id = float_to_int(float_time_id)
except ValueError:
print(f"{expr} does not represent an integer time index.")
return time_id


def get_time_ids_from_instances_index(
instances_index: InstancesTimeIndex, value_provider: ValueProvider
) -> List[int]:
time_ids = []
if isinstance(instances_index.expressions, list): # List[ExpressionNode]
for expr in instances_index.expressions:
time_ids.append(evaluate_time_id(expr, value_provider))

elif isinstance(instances_index.expressions, ExpressionRange): # ExpressionRange
start_id = evaluate_time_id(instances_index.expressions.start, value_provider)
stop_id = evaluate_time_id(instances_index.expressions.stop, value_provider)
step_id = 1
if instances_index.expressions.step is not None:
step_id = evaluate_time_id(instances_index.expressions.step, value_provider)
# ExpressionRange includes stop_id whereas range excludes it
time_ids = list(range(start_id, stop_id + 1, step_id))

return time_ids
26 changes: 26 additions & 0 deletions src/andromede/expression/linear_expression_efficient.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from andromede.expression.context_adder import add_component_context
from andromede.expression.equality import expressions_equal
from andromede.expression.evaluate import ValueProvider, evaluate
from andromede.expression.evaluate_parameters_efficient import resolve_coefficient
from andromede.expression.expression_efficient import (
ExpressionNodeEfficient,
ExpressionRange,
Expand Down Expand Up @@ -61,6 +62,10 @@
TimeShift,
TimeSum,
)
from andromede.simulation.resolved_linear_expression import (
ResolvedLinearExpression,
ResolvedTerm,
)


@dataclass(frozen=True)
Expand Down Expand Up @@ -416,6 +421,12 @@ def _substract_terms(lhs: T_val, rhs: T_val) -> T_val:
return dataclasses.replace(lhs, coefficient=lhs.coefficient - rhs.coefficient)


@dataclass(frozen=True)
class RowIndex:
time: int
scenario: int


class LinearExpressionEfficient:
"""
Represents a linear expression with respect to variable names, for example 10x + 5y + 2.
Expand Down Expand Up @@ -936,6 +947,21 @@ def add_component_context(self, component_id: str) -> "LinearExpressionEfficient
result_terms, result_constant, self.port_field_terms
)

def resolve_coefficient(
self, value_provider: ValueProvider, row_id: RowIndex
) -> ResolvedLinearExpression:

resolved_terms = []
for term in self.terms.values():
resolved_coeff = resolve_coefficient(
term.coefficient, value_provider, row_id
)
resolved_variable = ...
resolved_terms.append(ResolvedTerm(resolved_coeff, resolved_variable))

resolved_constant = resolve_coefficient(self.constant, value_provider, row_id)
return ResolvedLinearExpression(resolved_terms, resolved_constant)


def linear_expressions_equal(
lhs: LinearExpressionEfficient, rhs: LinearExpressionEfficient
Expand Down
Loading