diff --git a/grammar/Expr.g4 b/grammar/Expr.g4 index 072bf52e..9b21de7b 100644 --- a/grammar/Expr.g4 +++ b/grammar/Expr.g4 @@ -25,11 +25,11 @@ expr | expr op=('/' | '*') expr # muldiv | expr op=('+' | '-') expr # addsub | expr COMPARISON expr # comparison + | 'sum' '(' expr ')' # allTimeSum + | 'sum' '(' from=shift '..' to=shift ',' expr ')' # timeSum | IDENTIFIER '(' expr ')' # function - | IDENTIFIER '[' shift (',' shift)* ']' # timeShift - | IDENTIFIER '[' expr (',' expr )* ']' # timeIndex - | IDENTIFIER '[' shift1=shift '..' shift2=shift ']' # timeShiftRange - | IDENTIFIER '[' expr '..' expr ']' # timeRange + | IDENTIFIER '[' shift ']' # timeShift + | IDENTIFIER '[' expr ']' # timeIndex ; atom diff --git a/src/andromede/expression/copy.py b/src/andromede/expression/copy.py index c135ee59..be61c1a8 100644 --- a/src/andromede/expression/copy.py +++ b/src/andromede/expression/copy.py @@ -11,30 +11,25 @@ # This file is part of the Antares project. from dataclasses import dataclass -from typing import List, Union, cast +from typing import List, cast from .expression import ( - AdditionNode, + AllTimeSumNode, ComparisonNode, ComponentParameterNode, ComponentVariableNode, - DivisionNode, ExpressionNode, - ExpressionRange, - InstancesTimeIndex, LiteralNode, - MultiplicationNode, - NegationNode, ParameterNode, PortFieldAggregatorNode, PortFieldNode, ScenarioOperatorNode, - SubstractionNode, - TimeAggregatorNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, VariableNode, ) -from .visitor import ExpressionVisitor, ExpressionVisitorOperations, T, visit +from .visitor import ExpressionVisitorOperations, visit @dataclass(frozen=True) @@ -63,38 +58,21 @@ def comp_variable(self, node: ComponentVariableNode) -> ExpressionNode: def comp_parameter(self, node: ComponentParameterNode) -> ExpressionNode: return ComponentParameterNode(node.component_id, node.name) - def copy_expression_range( - self, expression_range: ExpressionRange - ) -> ExpressionRange: - return ExpressionRange( - start=visit(expression_range.start, self), - stop=visit(expression_range.stop, self), - step=visit(expression_range.step, self) - if expression_range.step is not None - else None, - ) + def time_shift(self, node: TimeShiftNode) -> ExpressionNode: + return TimeShiftNode(visit(node.operand, self), visit(node.time_shift, self)) + + def time_eval(self, node: TimeEvalNode) -> ExpressionNode: + return TimeShiftNode(visit(node.operand, self), visit(node.eval_time, self)) - def copy_instances_index( - self, instances_index: InstancesTimeIndex - ) -> InstancesTimeIndex: - expressions = instances_index.expressions - if isinstance(expressions, ExpressionRange): - return InstancesTimeIndex(self.copy_expression_range(expressions)) - if isinstance(expressions, list): - expressions_list = cast(List[ExpressionNode], expressions) - copy = [visit(e, self) for e in expressions_list] - return InstancesTimeIndex(copy) - raise ValueError("Unexpected type in instances index") - - def time_operator(self, node: TimeOperatorNode) -> ExpressionNode: - return TimeOperatorNode( + def time_sum(self, node: TimeSumNode) -> ExpressionNode: + return TimeSumNode( visit(node.operand, self), - node.name, - self.copy_instances_index(node.instances_index), + visit(node.from_time, self), + visit(node.to_time, self), ) - def time_aggregator(self, node: TimeAggregatorNode) -> ExpressionNode: - return TimeAggregatorNode(visit(node.operand, self), node.name, node.stay_roll) + def all_time_sum(self, node: AllTimeSumNode) -> ExpressionNode: + return AllTimeSumNode(visit(node.operand, self)) def scenario_operator(self, node: ScenarioOperatorNode) -> ExpressionNode: return ScenarioOperatorNode(visit(node.operand, self), node.name) diff --git a/src/andromede/expression/degree.py b/src/andromede/expression/degree.py index cfd175cd..c8006e83 100644 --- a/src/andromede/expression/degree.py +++ b/src/andromede/expression/degree.py @@ -12,11 +12,14 @@ import andromede.expression.scenario_operator from andromede.expression.expression import ( + AllTimeSumNode, ComponentParameterNode, ComponentVariableNode, PortFieldAggregatorNode, PortFieldNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, ) from .expression import ( @@ -30,7 +33,6 @@ ParameterNode, ScenarioOperatorNode, SubstractionNode, - TimeAggregatorNode, VariableNode, ) from .visitor import ExpressionVisitor, T, visit @@ -78,17 +80,17 @@ def comp_variable(self, node: ComponentVariableNode) -> int: def comp_parameter(self, node: ComponentParameterNode) -> int: return 0 - def time_operator(self, node: TimeOperatorNode) -> int: - if node.name in ["TimeShift", "TimeEvaluation"]: - return visit(node.operand, self) - else: - return NotImplemented - - def time_aggregator(self, node: TimeAggregatorNode) -> int: - if node.name in ["TimeSum"]: - return visit(node.operand, self) - else: - return NotImplemented + def time_shift(self, node: TimeShiftNode) -> int: + return visit(node.operand, self) + + def time_eval(self, node: TimeEvalNode) -> int: + return visit(node.operand, self) + + def time_sum(self, node: TimeSumNode) -> int: + return visit(node.operand, self) + + def all_time_sum(self, node: AllTimeSumNode) -> int: + return visit(node.operand, self) def scenario_operator(self, node: ScenarioOperatorNode) -> int: scenario_operator_cls = getattr( diff --git a/src/andromede/expression/equality.py b/src/andromede/expression/equality.py index a2deeb27..9851ee3f 100644 --- a/src/andromede/expression/equality.py +++ b/src/andromede/expression/equality.py @@ -27,14 +27,14 @@ VariableNode, ) from andromede.expression.expression import ( + AllTimeSumNode, BinaryOperatorNode, - ExpressionRange, - InstancesTimeIndex, PortFieldAggregatorNode, PortFieldNode, ScenarioOperatorNode, - TimeAggregatorNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, ) @@ -76,12 +76,14 @@ def visit(self, left: ExpressionNode, right: ExpressionNode) -> bool: return self.variable(left, right) if isinstance(left, ParameterNode) and isinstance(right, ParameterNode): return self.parameter(left, right) - if isinstance(left, TimeOperatorNode) and isinstance(right, TimeOperatorNode): - return self.time_operator(left, right) - if isinstance(left, TimeAggregatorNode) and isinstance( - right, TimeAggregatorNode - ): - return self.time_aggregator(left, right) + if isinstance(left, TimeShiftNode) and isinstance(right, TimeShiftNode): + return self.time_shift(left, right) + if isinstance(left, TimeEvalNode) and isinstance(right, TimeEvalNode): + return self.time_eval(left, right) + if isinstance(left, TimeSumNode) and isinstance(right, TimeSumNode): + return self.time_sum(left, right) + if isinstance(left, AllTimeSumNode) and isinstance(right, AllTimeSumNode): + return self.all_time_sum(left, right) if isinstance(left, ScenarioOperatorNode) and isinstance( right, ScenarioOperatorNode ): @@ -130,42 +132,26 @@ def variable(self, left: VariableNode, right: VariableNode) -> bool: def parameter(self, left: ParameterNode, right: ParameterNode) -> bool: return left.name == right.name - def expression_range(self, left: ExpressionRange, right: ExpressionRange) -> bool: - if not self.visit(left.start, right.start): - return False - if not self.visit(left.stop, right.stop): - return False - if left.step is not None and right.step is not None: - return self.visit(left.step, right.step) - return left.step is None and right.step is None - - def instances_index(self, lhs: InstancesTimeIndex, rhs: InstancesTimeIndex) -> bool: - if isinstance(lhs.expressions, ExpressionRange) and isinstance( - rhs.expressions, ExpressionRange - ): - return self.expression_range(lhs.expressions, rhs.expressions) - if isinstance(lhs.expressions, list) and isinstance(rhs.expressions, list): - return len(lhs.expressions) == len(rhs.expressions) and all( - self.visit(l, r) for l, r in zip(lhs.expressions, rhs.expressions) - ) - return False + def time_shift(self, left: TimeShiftNode, right: TimeShiftNode) -> bool: + return self.visit(left.time_shift, right.time_shift) and self.visit( + left.operand, right.operand + ) - def time_operator(self, left: TimeOperatorNode, right: TimeOperatorNode) -> bool: - return ( - left.name == right.name - and self.instances_index(left.instances_index, right.instances_index) - and self.visit(left.operand, right.operand) + def time_eval(self, left: TimeEvalNode, right: TimeEvalNode) -> bool: + return self.visit(left.eval_time, right.eval_time) and self.visit( + left.operand, right.operand ) - def time_aggregator( - self, left: TimeAggregatorNode, right: TimeAggregatorNode - ) -> bool: + def time_sum(self, left: TimeSumNode, right: TimeSumNode) -> bool: return ( - left.name == right.name - and left.stay_roll == right.stay_roll + self.visit(left.from_time, right.from_time) + and self.visit(left.to_time, right.to_time) and self.visit(left.operand, right.operand) ) + def all_time_sum(self, left: AllTimeSumNode, right: AllTimeSumNode) -> bool: + return self.visit(left.operand, right.operand) + def scenario_operator( self, left: ScenarioOperatorNode, right: ScenarioOperatorNode ) -> bool: diff --git a/src/andromede/expression/evaluate.py b/src/andromede/expression/evaluate.py index b51c0e86..642a5391 100644 --- a/src/andromede/expression/evaluate.py +++ b/src/andromede/expression/evaluate.py @@ -15,28 +15,25 @@ from typing import Dict from andromede.expression.expression import ( + AllTimeSumNode, ComponentParameterNode, ComponentVariableNode, PortFieldAggregatorNode, PortFieldNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, ) from .expression import ( - AdditionNode, ComparisonNode, - DivisionNode, ExpressionNode, LiteralNode, - MultiplicationNode, - NegationNode, ParameterNode, ScenarioOperatorNode, - SubstractionNode, - TimeAggregatorNode, VariableNode, ) -from .visitor import ExpressionVisitor, ExpressionVisitorOperations, T, visit +from .visitor import ExpressionVisitorOperations, visit class ValueProvider(ABC): @@ -120,10 +117,16 @@ def comp_parameter(self, node: ComponentParameterNode) -> float: 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: + def time_shift(self, node: TimeShiftNode) -> float: raise NotImplementedError() - def time_aggregator(self, node: TimeAggregatorNode) -> float: + def time_eval(self, node: TimeEvalNode) -> float: + raise NotImplementedError() + + def time_sum(self, node: TimeSumNode) -> float: + raise NotImplementedError() + + def all_time_sum(self, node: AllTimeSumNode) -> float: raise NotImplementedError() def scenario_operator(self, node: ScenarioOperatorNode) -> float: @@ -156,8 +159,14 @@ def parameter(self, node: ParameterNode) -> float: ) 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_shift(self, node: TimeShiftNode) -> float: + raise ValueError("An instance index expression cannot contain time shift") + + def time_eval(self, node: TimeEvalNode) -> float: + raise ValueError("An instance index expression cannot contain time eval") + + def time_sum(self, node: TimeSumNode) -> float: + raise ValueError("An instance index expression cannot contain time sum") - def time_aggregator(self, node: TimeAggregatorNode) -> float: - raise ValueError("An instance index expression cannot contain time aggregator") + def all_time_sum(self, node: AllTimeSumNode) -> float: + raise ValueError("An instance index expression cannot contain time sum") diff --git a/src/andromede/expression/evaluate_parameters.py b/src/andromede/expression/evaluate_parameters.py index 7c734260..f7c590df 100644 --- a/src/andromede/expression/evaluate_parameters.py +++ b/src/andromede/expression/evaluate_parameters.py @@ -20,8 +20,6 @@ from .expression import ( ComponentParameterNode, ExpressionNode, - ExpressionRange, - InstancesTimeIndex, LiteralNode, ParameterNode, ) @@ -79,21 +77,21 @@ def evaluate_time_id(expr: ExpressionNode, value_provider: ValueProvider) -> int 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 +# 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 diff --git a/src/andromede/expression/expression.py b/src/andromede/expression/expression.py index eb03dd5b..107e8a9f 100644 --- a/src/andromede/expression/expression.py +++ b/src/andromede/expression/expression.py @@ -15,17 +15,13 @@ """ import enum import inspect -from dataclasses import dataclass, field -from typing import Any, Callable, List, Optional, Sequence, Union +from dataclasses import dataclass +from typing import Any, Callable, Optional, Sequence, Union import andromede.expression.port_operator import andromede.expression.scenario_operator -import andromede.expression.time_operator - -class Instances(enum.Enum): - SIMPLE = "SIMPLE" - MULTIPLE = "MULTIPLE" +AnyExpression = Union[int, float, "ExpressionNode"] @dataclass(frozen=True) @@ -40,8 +36,6 @@ class ExpressionNode: >>> expr = -var('x') + 5 / param('p') """ - instances: Instances = field(init=False, default=Instances.SIMPLE) - def __neg__(self) -> "ExpressionNode": return NegationNode(self) @@ -82,13 +76,20 @@ def __ge__(self, rhs: Any) -> "ExpressionNode": def __eq__(self, rhs: Any) -> "ExpressionNode": # type: ignore return _apply_if_node(rhs, lambda x: ComparisonNode(self, x, Comparator.EQUAL)) - def sum(self) -> "ExpressionNode": - if isinstance(self, TimeOperatorNode): - return TimeAggregatorNode(self, "TimeSum", stay_roll=True) - else: - return _apply_if_node( - self, lambda x: TimeAggregatorNode(x, "TimeSum", stay_roll=False) - ) + def time_sum( + self, + from_shift: Optional[AnyExpression] = None, + to_shift: Optional[AnyExpression] = None, + ) -> "ExpressionNode": + if from_shift is None and to_shift is None: + return AllTimeSumNode(self) + if from_shift is None or to_shift is None: + raise ValueError("Both time bounds of a time sum must be defined.") + return TimeSumNode( + operand=self, + from_time=_wrap_in_node(from_shift), + to_time=_wrap_in_node(to_shift), + ) def sum_connections(self) -> "ExpressionNode": if isinstance(self, PortFieldNode): @@ -97,29 +98,11 @@ def sum_connections(self) -> "ExpressionNode": f"sum_connections() applies only for PortFieldNode, whereas the current node is of type {type(self)}." ) - def shift( - self, - expressions: Union[ - int, "ExpressionNode", List["ExpressionNode"], "ExpressionRange" - ], - ) -> "ExpressionNode": - return _apply_if_node( - self, - lambda x: TimeOperatorNode(x, "TimeShift", InstancesTimeIndex(expressions)), - ) + def shift(self, shift: AnyExpression) -> "ExpressionNode": + return TimeShiftNode(self, _wrap_in_node(shift)) - def eval( - self, - expressions: Union[ - int, "ExpressionNode", List["ExpressionNode"], "ExpressionRange" - ], - ) -> "ExpressionNode": - return _apply_if_node( - self, - lambda x: TimeOperatorNode( - x, "TimeEvaluation", InstancesTimeIndex(expressions) - ), - ) + def eval(self, time: AnyExpression) -> "ExpressionNode": + return TimeEvalNode(self, _wrap_in_node(time)) def expec(self) -> "ExpressionNode": return _apply_if_node(self, lambda x: ScenarioOperatorNode(x, "Expectation")) @@ -226,9 +209,6 @@ def literal(value: float) -> LiteralNode: class UnaryOperatorNode(ExpressionNode): operand: ExpressionNode - def __post_init__(self) -> None: - object.__setattr__(self, "instances", self.operand.instances) - @dataclass(frozen=True, eq=False) class PortFieldAggregatorNode(UnaryOperatorNode): @@ -258,18 +238,6 @@ class BinaryOperatorNode(ExpressionNode): left: ExpressionNode right: ExpressionNode - def __post_init__(self) -> None: - binary_operator_post_init(self, "apply binary operation with") - - -def binary_operator_post_init(node: BinaryOperatorNode, operation: str) -> None: - if node.left.instances != node.right.instances: - raise ValueError( - f"Cannot {operation} {node.left} and {node.right} as they do not have the same number of instances." - ) - else: - object.__setattr__(node, "instances", node.left.instances) - class Comparator(enum.Enum): LESS_THAN = "LESS_THAN" @@ -281,148 +249,51 @@ class Comparator(enum.Enum): class ComparisonNode(BinaryOperatorNode): comparator: Comparator - def __post_init__(self) -> None: - binary_operator_post_init(self, "compare") - @dataclass(frozen=True, eq=False) class AdditionNode(BinaryOperatorNode): - def __post_init__(self) -> None: - binary_operator_post_init(self, "add") + pass @dataclass(frozen=True, eq=False) class SubstractionNode(BinaryOperatorNode): - def __post_init__(self) -> None: - binary_operator_post_init(self, "substract") + pass @dataclass(frozen=True, eq=False) class MultiplicationNode(BinaryOperatorNode): - def __post_init__(self) -> None: - binary_operator_post_init(self, "multiply") + pass @dataclass(frozen=True, eq=False) class DivisionNode(BinaryOperatorNode): - def __post_init__(self) -> None: - binary_operator_post_init(self, "divide") + pass @dataclass(frozen=True, eq=False) -class ExpressionRange: - start: ExpressionNode - stop: ExpressionNode - step: Optional[ExpressionNode] = None - - def __post_init__(self) -> None: - for attribute in self.__dict__: - value = getattr(self, attribute) - object.__setattr__( - self, attribute, _wrap_in_node(value) if value is not None else value - ) - - -IntOrExpr = Union[int, ExpressionNode] - +class TimeShiftNode(UnaryOperatorNode): + time_shift: ExpressionNode -def expression_range( - start: IntOrExpr, stop: IntOrExpr, step: Optional[IntOrExpr] = None -) -> ExpressionRange: - return ExpressionRange( - start=_wrap_in_node(start), - stop=_wrap_in_node(stop), - step=None if step is None else _wrap_in_node(step), - ) - -@dataclass -class InstancesTimeIndex: - """ - Defines a set of time indices on which a time operator operates. - - In particular, it defines time indices created by the shift operator. - - The actual indices can either be defined as a time range defined by - 2 expression, or as a list of expressions. - """ - - expressions: Union[List[ExpressionNode], ExpressionRange] - - def __init__( - self, - expressions: Union[int, ExpressionNode, List[ExpressionNode], ExpressionRange], - ) -> None: - if not isinstance(expressions, (int, ExpressionNode, list, ExpressionRange)): - raise TypeError( - f"{expressions} must be of type among {{int, ExpressionNode, List[ExpressionNode], ExpressionRange}}" - ) - if isinstance(expressions, list) and not all( - isinstance(x, ExpressionNode) for x in expressions - ): - raise TypeError( - f"All elements of {expressions} must be of type ExpressionNode" - ) - - if isinstance(expressions, (int, ExpressionNode)): - self.expressions = [_wrap_in_node(expressions)] - else: - self.expressions = expressions - - def is_simple(self) -> bool: - if isinstance(self.expressions, list): - return len(self.expressions) == 1 - else: - # TODO: We could also check that if a range only includes literal nodes, compute the length of the range, if it's one return True. This is more complicated, I do not know if we want to do this - return False +@dataclass(frozen=True, eq=False) +class TimeEvalNode(UnaryOperatorNode): + eval_time: ExpressionNode @dataclass(frozen=True, eq=False) -class TimeOperatorNode(UnaryOperatorNode): - name: str - instances_index: InstancesTimeIndex - - def __post_init__(self) -> None: - valid_names = [ - cls.__name__ - for _, cls in inspect.getmembers( - andromede.expression.time_operator, inspect.isclass - ) - if issubclass(cls, andromede.expression.time_operator.TimeOperator) - ] - if self.name not in valid_names: - raise ValueError( - f"{self.name} is not a valid time aggregator, valid time aggregators are {valid_names}" - ) - if self.operand.instances == Instances.SIMPLE: - if self.instances_index.is_simple(): - object.__setattr__(self, "instances", Instances.SIMPLE) - else: - object.__setattr__(self, "instances", Instances.MULTIPLE) - else: - raise ValueError( - "Cannot apply time operator on an expression that already represents multiple instances" - ) +class TimeSumNode(UnaryOperatorNode): + from_time: ExpressionNode + to_time: ExpressionNode @dataclass(frozen=True, eq=False) -class TimeAggregatorNode(UnaryOperatorNode): - name: str - stay_roll: bool +class AllTimeSumNode(UnaryOperatorNode): + """ + Separate from time sum node because it's actually a quite different operation: + In particular, this changes the time indexing. + """ - def __post_init__(self) -> None: - valid_names = [ - cls.__name__ - for _, cls in inspect.getmembers( - andromede.expression.time_operator, inspect.isclass - ) - if issubclass(cls, andromede.expression.time_operator.TimeAggregator) - ] - if self.name not in valid_names: - raise ValueError( - f"{self.name} is not a valid time aggregator, valid time aggregators are {valid_names}" - ) - object.__setattr__(self, "instances", Instances.SIMPLE) + pass @dataclass(frozen=True, eq=False) @@ -441,7 +312,6 @@ def __post_init__(self) -> None: raise ValueError( f"{self.name} is not a valid scenario operator, valid scenario operators are {valid_names}" ) - object.__setattr__(self, "instances", Instances.SIMPLE) def sum_expressions(expressions: Sequence[ExpressionNode]) -> ExpressionNode: diff --git a/src/andromede/expression/indexing.py b/src/andromede/expression/indexing.py index 11051dd5..7f09d08a 100644 --- a/src/andromede/expression/indexing.py +++ b/src/andromede/expression/indexing.py @@ -13,11 +13,11 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -import andromede.expression.time_operator from andromede.expression.indexing_structure import IndexingStructure from .expression import ( AdditionNode, + AllTimeSumNode, ComparisonNode, ComponentParameterNode, ComponentVariableNode, @@ -31,8 +31,9 @@ PortFieldNode, ScenarioOperatorNode, SubstractionNode, - TimeAggregatorNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, VariableNode, ) from .visitor import ExpressionVisitor, T, visit @@ -109,18 +110,17 @@ def comp_parameter(self, node: ComponentParameterNode) -> IndexingStructure: node.component_id, node.name ) - def time_operator(self, node: TimeOperatorNode) -> IndexingStructure: - time_operator_cls = getattr(andromede.expression.time_operator, node.name) - if time_operator_cls.rolling(): - return visit(node.operand, self) - else: - return IndexingStructure(False, visit(node.operand, self).scenario) - - def time_aggregator(self, node: TimeAggregatorNode) -> IndexingStructure: - if node.stay_roll: - return visit(node.operand, self) - else: - return IndexingStructure(False, visit(node.operand, self).scenario) + def time_shift(self, node: TimeShiftNode) -> IndexingStructure: + return visit(node.operand, self) + + def time_eval(self, node: TimeEvalNode) -> IndexingStructure: + return visit(node.operand, self) + + def time_sum(self, node: TimeSumNode) -> IndexingStructure: + return visit(node.operand, self) + + def all_time_sum(self, node: AllTimeSumNode) -> IndexingStructure: + return IndexingStructure(False, visit(node.operand, self).scenario) def scenario_operator(self, node: ScenarioOperatorNode) -> IndexingStructure: return IndexingStructure(visit(node.operand, self).time, False) diff --git a/src/andromede/expression/parsing/antlr/Expr.interp b/src/andromede/expression/parsing/antlr/Expr.interp index bf05ae28..d4189d97 100644 --- a/src/andromede/expression/parsing/antlr/Expr.interp +++ b/src/andromede/expression/parsing/antlr/Expr.interp @@ -7,10 +7,11 @@ null '/' '*' '+' -'[' +'sum' +'..' ',' +'[' ']' -'..' null 't' null @@ -30,6 +31,7 @@ null null null null +null NUMBER TIME IDENTIFIER @@ -46,4 +48,4 @@ right_expr atn: -[4, 1, 16, 131, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 37, 8, 1, 10, 1, 12, 1, 40, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 49, 8, 1, 10, 1, 12, 1, 52, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 70, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 81, 8, 1, 10, 1, 12, 1, 84, 9, 1, 1, 2, 1, 2, 3, 2, 88, 8, 2, 1, 3, 1, 3, 3, 3, 92, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 102, 8, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 110, 8, 4, 10, 4, 12, 4, 113, 9, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 121, 8, 5, 1, 5, 1, 5, 1, 5, 5, 5, 126, 8, 5, 10, 5, 12, 5, 129, 9, 5, 1, 5, 0, 3, 2, 8, 10, 6, 0, 2, 4, 6, 8, 10, 0, 2, 1, 0, 5, 6, 2, 0, 2, 2, 7, 7, 144, 0, 12, 1, 0, 0, 0, 2, 69, 1, 0, 0, 0, 4, 87, 1, 0, 0, 0, 6, 89, 1, 0, 0, 0, 8, 101, 1, 0, 0, 0, 10, 120, 1, 0, 0, 0, 12, 13, 3, 2, 1, 0, 13, 14, 5, 0, 0, 1, 14, 1, 1, 0, 0, 0, 15, 16, 6, 1, -1, 0, 16, 70, 3, 4, 2, 0, 17, 18, 5, 14, 0, 0, 18, 19, 5, 1, 0, 0, 19, 70, 5, 14, 0, 0, 20, 21, 5, 2, 0, 0, 21, 70, 3, 2, 1, 10, 22, 23, 5, 3, 0, 0, 23, 24, 3, 2, 1, 0, 24, 25, 5, 4, 0, 0, 25, 70, 1, 0, 0, 0, 26, 27, 5, 14, 0, 0, 27, 28, 5, 3, 0, 0, 28, 29, 3, 2, 1, 0, 29, 30, 5, 4, 0, 0, 30, 70, 1, 0, 0, 0, 31, 32, 5, 14, 0, 0, 32, 33, 5, 8, 0, 0, 33, 38, 3, 6, 3, 0, 34, 35, 5, 9, 0, 0, 35, 37, 3, 6, 3, 0, 36, 34, 1, 0, 0, 0, 37, 40, 1, 0, 0, 0, 38, 36, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 41, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 41, 42, 5, 10, 0, 0, 42, 70, 1, 0, 0, 0, 43, 44, 5, 14, 0, 0, 44, 45, 5, 8, 0, 0, 45, 50, 3, 2, 1, 0, 46, 47, 5, 9, 0, 0, 47, 49, 3, 2, 1, 0, 48, 46, 1, 0, 0, 0, 49, 52, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 50, 51, 1, 0, 0, 0, 51, 53, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 53, 54, 5, 10, 0, 0, 54, 70, 1, 0, 0, 0, 55, 56, 5, 14, 0, 0, 56, 57, 5, 8, 0, 0, 57, 58, 3, 6, 3, 0, 58, 59, 5, 11, 0, 0, 59, 60, 3, 6, 3, 0, 60, 61, 5, 10, 0, 0, 61, 70, 1, 0, 0, 0, 62, 63, 5, 14, 0, 0, 63, 64, 5, 8, 0, 0, 64, 65, 3, 2, 1, 0, 65, 66, 5, 11, 0, 0, 66, 67, 3, 2, 1, 0, 67, 68, 5, 10, 0, 0, 68, 70, 1, 0, 0, 0, 69, 15, 1, 0, 0, 0, 69, 17, 1, 0, 0, 0, 69, 20, 1, 0, 0, 0, 69, 22, 1, 0, 0, 0, 69, 26, 1, 0, 0, 0, 69, 31, 1, 0, 0, 0, 69, 43, 1, 0, 0, 0, 69, 55, 1, 0, 0, 0, 69, 62, 1, 0, 0, 0, 70, 82, 1, 0, 0, 0, 71, 72, 10, 8, 0, 0, 72, 73, 7, 0, 0, 0, 73, 81, 3, 2, 1, 9, 74, 75, 10, 7, 0, 0, 75, 76, 7, 1, 0, 0, 76, 81, 3, 2, 1, 8, 77, 78, 10, 6, 0, 0, 78, 79, 5, 15, 0, 0, 79, 81, 3, 2, 1, 7, 80, 71, 1, 0, 0, 0, 80, 74, 1, 0, 0, 0, 80, 77, 1, 0, 0, 0, 81, 84, 1, 0, 0, 0, 82, 80, 1, 0, 0, 0, 82, 83, 1, 0, 0, 0, 83, 3, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 85, 88, 5, 12, 0, 0, 86, 88, 5, 14, 0, 0, 87, 85, 1, 0, 0, 0, 87, 86, 1, 0, 0, 0, 88, 5, 1, 0, 0, 0, 89, 91, 5, 13, 0, 0, 90, 92, 3, 8, 4, 0, 91, 90, 1, 0, 0, 0, 91, 92, 1, 0, 0, 0, 92, 7, 1, 0, 0, 0, 93, 94, 6, 4, -1, 0, 94, 95, 7, 1, 0, 0, 95, 102, 3, 4, 2, 0, 96, 97, 7, 1, 0, 0, 97, 98, 5, 3, 0, 0, 98, 99, 3, 2, 1, 0, 99, 100, 5, 4, 0, 0, 100, 102, 1, 0, 0, 0, 101, 93, 1, 0, 0, 0, 101, 96, 1, 0, 0, 0, 102, 111, 1, 0, 0, 0, 103, 104, 10, 4, 0, 0, 104, 105, 7, 0, 0, 0, 105, 110, 3, 10, 5, 0, 106, 107, 10, 3, 0, 0, 107, 108, 7, 1, 0, 0, 108, 110, 3, 10, 5, 0, 109, 103, 1, 0, 0, 0, 109, 106, 1, 0, 0, 0, 110, 113, 1, 0, 0, 0, 111, 109, 1, 0, 0, 0, 111, 112, 1, 0, 0, 0, 112, 9, 1, 0, 0, 0, 113, 111, 1, 0, 0, 0, 114, 115, 6, 5, -1, 0, 115, 116, 5, 3, 0, 0, 116, 117, 3, 2, 1, 0, 117, 118, 5, 4, 0, 0, 118, 121, 1, 0, 0, 0, 119, 121, 3, 4, 2, 0, 120, 114, 1, 0, 0, 0, 120, 119, 1, 0, 0, 0, 121, 127, 1, 0, 0, 0, 122, 123, 10, 3, 0, 0, 123, 124, 7, 0, 0, 0, 124, 126, 3, 10, 5, 4, 125, 122, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 11, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 12, 38, 50, 69, 80, 82, 87, 91, 101, 109, 111, 120, 127] \ No newline at end of file +[4, 1, 17, 117, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 56, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 67, 8, 1, 10, 1, 12, 1, 70, 9, 1, 1, 2, 1, 2, 3, 2, 74, 8, 2, 1, 3, 1, 3, 3, 3, 78, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 88, 8, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 96, 8, 4, 10, 4, 12, 4, 99, 9, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 107, 8, 5, 1, 5, 1, 5, 1, 5, 5, 5, 112, 8, 5, 10, 5, 12, 5, 115, 9, 5, 1, 5, 0, 3, 2, 8, 10, 6, 0, 2, 4, 6, 8, 10, 0, 2, 1, 0, 5, 6, 2, 0, 2, 2, 7, 7, 128, 0, 12, 1, 0, 0, 0, 2, 55, 1, 0, 0, 0, 4, 73, 1, 0, 0, 0, 6, 75, 1, 0, 0, 0, 8, 87, 1, 0, 0, 0, 10, 106, 1, 0, 0, 0, 12, 13, 3, 2, 1, 0, 13, 14, 5, 0, 0, 1, 14, 1, 1, 0, 0, 0, 15, 16, 6, 1, -1, 0, 16, 56, 3, 4, 2, 0, 17, 18, 5, 15, 0, 0, 18, 19, 5, 1, 0, 0, 19, 56, 5, 15, 0, 0, 20, 21, 5, 2, 0, 0, 21, 56, 3, 2, 1, 10, 22, 23, 5, 3, 0, 0, 23, 24, 3, 2, 1, 0, 24, 25, 5, 4, 0, 0, 25, 56, 1, 0, 0, 0, 26, 27, 5, 8, 0, 0, 27, 28, 5, 3, 0, 0, 28, 29, 3, 2, 1, 0, 29, 30, 5, 4, 0, 0, 30, 56, 1, 0, 0, 0, 31, 32, 5, 8, 0, 0, 32, 33, 5, 3, 0, 0, 33, 34, 3, 6, 3, 0, 34, 35, 5, 9, 0, 0, 35, 36, 3, 6, 3, 0, 36, 37, 5, 10, 0, 0, 37, 38, 3, 2, 1, 0, 38, 39, 5, 4, 0, 0, 39, 56, 1, 0, 0, 0, 40, 41, 5, 15, 0, 0, 41, 42, 5, 3, 0, 0, 42, 43, 3, 2, 1, 0, 43, 44, 5, 4, 0, 0, 44, 56, 1, 0, 0, 0, 45, 46, 5, 15, 0, 0, 46, 47, 5, 11, 0, 0, 47, 48, 3, 6, 3, 0, 48, 49, 5, 12, 0, 0, 49, 56, 1, 0, 0, 0, 50, 51, 5, 15, 0, 0, 51, 52, 5, 11, 0, 0, 52, 53, 3, 2, 1, 0, 53, 54, 5, 12, 0, 0, 54, 56, 1, 0, 0, 0, 55, 15, 1, 0, 0, 0, 55, 17, 1, 0, 0, 0, 55, 20, 1, 0, 0, 0, 55, 22, 1, 0, 0, 0, 55, 26, 1, 0, 0, 0, 55, 31, 1, 0, 0, 0, 55, 40, 1, 0, 0, 0, 55, 45, 1, 0, 0, 0, 55, 50, 1, 0, 0, 0, 56, 68, 1, 0, 0, 0, 57, 58, 10, 8, 0, 0, 58, 59, 7, 0, 0, 0, 59, 67, 3, 2, 1, 9, 60, 61, 10, 7, 0, 0, 61, 62, 7, 1, 0, 0, 62, 67, 3, 2, 1, 8, 63, 64, 10, 6, 0, 0, 64, 65, 5, 16, 0, 0, 65, 67, 3, 2, 1, 7, 66, 57, 1, 0, 0, 0, 66, 60, 1, 0, 0, 0, 66, 63, 1, 0, 0, 0, 67, 70, 1, 0, 0, 0, 68, 66, 1, 0, 0, 0, 68, 69, 1, 0, 0, 0, 69, 3, 1, 0, 0, 0, 70, 68, 1, 0, 0, 0, 71, 74, 5, 13, 0, 0, 72, 74, 5, 15, 0, 0, 73, 71, 1, 0, 0, 0, 73, 72, 1, 0, 0, 0, 74, 5, 1, 0, 0, 0, 75, 77, 5, 14, 0, 0, 76, 78, 3, 8, 4, 0, 77, 76, 1, 0, 0, 0, 77, 78, 1, 0, 0, 0, 78, 7, 1, 0, 0, 0, 79, 80, 6, 4, -1, 0, 80, 81, 7, 1, 0, 0, 81, 88, 3, 4, 2, 0, 82, 83, 7, 1, 0, 0, 83, 84, 5, 3, 0, 0, 84, 85, 3, 2, 1, 0, 85, 86, 5, 4, 0, 0, 86, 88, 1, 0, 0, 0, 87, 79, 1, 0, 0, 0, 87, 82, 1, 0, 0, 0, 88, 97, 1, 0, 0, 0, 89, 90, 10, 4, 0, 0, 90, 91, 7, 0, 0, 0, 91, 96, 3, 10, 5, 0, 92, 93, 10, 3, 0, 0, 93, 94, 7, 1, 0, 0, 94, 96, 3, 10, 5, 0, 95, 89, 1, 0, 0, 0, 95, 92, 1, 0, 0, 0, 96, 99, 1, 0, 0, 0, 97, 95, 1, 0, 0, 0, 97, 98, 1, 0, 0, 0, 98, 9, 1, 0, 0, 0, 99, 97, 1, 0, 0, 0, 100, 101, 6, 5, -1, 0, 101, 102, 5, 3, 0, 0, 102, 103, 3, 2, 1, 0, 103, 104, 5, 4, 0, 0, 104, 107, 1, 0, 0, 0, 105, 107, 3, 4, 2, 0, 106, 100, 1, 0, 0, 0, 106, 105, 1, 0, 0, 0, 107, 113, 1, 0, 0, 0, 108, 109, 10, 3, 0, 0, 109, 110, 7, 0, 0, 0, 110, 112, 3, 10, 5, 4, 111, 108, 1, 0, 0, 0, 112, 115, 1, 0, 0, 0, 113, 111, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 11, 1, 0, 0, 0, 115, 113, 1, 0, 0, 0, 10, 55, 66, 68, 73, 77, 87, 95, 97, 106, 113] \ No newline at end of file diff --git a/src/andromede/expression/parsing/antlr/Expr.tokens b/src/andromede/expression/parsing/antlr/Expr.tokens index 9401c83a..86857744 100644 --- a/src/andromede/expression/parsing/antlr/Expr.tokens +++ b/src/andromede/expression/parsing/antlr/Expr.tokens @@ -9,11 +9,12 @@ T__7=8 T__8=9 T__9=10 T__10=11 -NUMBER=12 -TIME=13 -IDENTIFIER=14 -COMPARISON=15 -WS=16 +T__11=12 +NUMBER=13 +TIME=14 +IDENTIFIER=15 +COMPARISON=16 +WS=17 '.'=1 '-'=2 '('=3 @@ -21,8 +22,9 @@ WS=16 '/'=5 '*'=6 '+'=7 -'['=8 -','=9 -']'=10 -'..'=11 -'t'=13 +'sum'=8 +'..'=9 +','=10 +'['=11 +']'=12 +'t'=14 diff --git a/src/andromede/expression/parsing/antlr/ExprLexer.interp b/src/andromede/expression/parsing/antlr/ExprLexer.interp index 2e85e1b7..43521ebb 100644 --- a/src/andromede/expression/parsing/antlr/ExprLexer.interp +++ b/src/andromede/expression/parsing/antlr/ExprLexer.interp @@ -7,10 +7,11 @@ null '/' '*' '+' -'[' +'sum' +'..' ',' +'[' ']' -'..' null 't' null @@ -30,6 +31,7 @@ null null null null +null NUMBER TIME IDENTIFIER @@ -48,6 +50,7 @@ T__7 T__8 T__9 T__10 +T__11 DIGIT CHAR CHAR_OR_DIGIT @@ -65,4 +68,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 16, 103, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 3, 13, 69, 8, 13, 1, 14, 4, 14, 72, 8, 14, 11, 14, 12, 14, 73, 1, 14, 1, 14, 4, 14, 78, 8, 14, 11, 14, 12, 14, 79, 3, 14, 82, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 5, 16, 88, 8, 16, 10, 16, 12, 16, 91, 9, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 98, 8, 17, 1, 18, 1, 18, 1, 18, 1, 18, 0, 0, 19, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 0, 25, 0, 27, 0, 29, 12, 31, 13, 33, 14, 35, 15, 37, 16, 1, 0, 3, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 106, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 1, 39, 1, 0, 0, 0, 3, 41, 1, 0, 0, 0, 5, 43, 1, 0, 0, 0, 7, 45, 1, 0, 0, 0, 9, 47, 1, 0, 0, 0, 11, 49, 1, 0, 0, 0, 13, 51, 1, 0, 0, 0, 15, 53, 1, 0, 0, 0, 17, 55, 1, 0, 0, 0, 19, 57, 1, 0, 0, 0, 21, 59, 1, 0, 0, 0, 23, 62, 1, 0, 0, 0, 25, 64, 1, 0, 0, 0, 27, 68, 1, 0, 0, 0, 29, 71, 1, 0, 0, 0, 31, 83, 1, 0, 0, 0, 33, 85, 1, 0, 0, 0, 35, 97, 1, 0, 0, 0, 37, 99, 1, 0, 0, 0, 39, 40, 5, 46, 0, 0, 40, 2, 1, 0, 0, 0, 41, 42, 5, 45, 0, 0, 42, 4, 1, 0, 0, 0, 43, 44, 5, 40, 0, 0, 44, 6, 1, 0, 0, 0, 45, 46, 5, 41, 0, 0, 46, 8, 1, 0, 0, 0, 47, 48, 5, 47, 0, 0, 48, 10, 1, 0, 0, 0, 49, 50, 5, 42, 0, 0, 50, 12, 1, 0, 0, 0, 51, 52, 5, 43, 0, 0, 52, 14, 1, 0, 0, 0, 53, 54, 5, 91, 0, 0, 54, 16, 1, 0, 0, 0, 55, 56, 5, 44, 0, 0, 56, 18, 1, 0, 0, 0, 57, 58, 5, 93, 0, 0, 58, 20, 1, 0, 0, 0, 59, 60, 5, 46, 0, 0, 60, 61, 5, 46, 0, 0, 61, 22, 1, 0, 0, 0, 62, 63, 7, 0, 0, 0, 63, 24, 1, 0, 0, 0, 64, 65, 7, 1, 0, 0, 65, 26, 1, 0, 0, 0, 66, 69, 3, 25, 12, 0, 67, 69, 3, 23, 11, 0, 68, 66, 1, 0, 0, 0, 68, 67, 1, 0, 0, 0, 69, 28, 1, 0, 0, 0, 70, 72, 3, 23, 11, 0, 71, 70, 1, 0, 0, 0, 72, 73, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 81, 1, 0, 0, 0, 75, 77, 5, 46, 0, 0, 76, 78, 3, 23, 11, 0, 77, 76, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 82, 1, 0, 0, 0, 81, 75, 1, 0, 0, 0, 81, 82, 1, 0, 0, 0, 82, 30, 1, 0, 0, 0, 83, 84, 5, 116, 0, 0, 84, 32, 1, 0, 0, 0, 85, 89, 3, 25, 12, 0, 86, 88, 3, 27, 13, 0, 87, 86, 1, 0, 0, 0, 88, 91, 1, 0, 0, 0, 89, 87, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 34, 1, 0, 0, 0, 91, 89, 1, 0, 0, 0, 92, 98, 5, 61, 0, 0, 93, 94, 5, 62, 0, 0, 94, 98, 5, 61, 0, 0, 95, 96, 5, 60, 0, 0, 96, 98, 5, 61, 0, 0, 97, 92, 1, 0, 0, 0, 97, 93, 1, 0, 0, 0, 97, 95, 1, 0, 0, 0, 98, 36, 1, 0, 0, 0, 99, 100, 7, 2, 0, 0, 100, 101, 1, 0, 0, 0, 101, 102, 6, 18, 0, 0, 102, 38, 1, 0, 0, 0, 7, 0, 68, 73, 79, 81, 89, 97, 1, 6, 0, 0] \ No newline at end of file +[4, 0, 17, 109, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 3, 14, 75, 8, 14, 1, 15, 4, 15, 78, 8, 15, 11, 15, 12, 15, 79, 1, 15, 1, 15, 4, 15, 84, 8, 15, 11, 15, 12, 15, 85, 3, 15, 88, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 5, 17, 94, 8, 17, 10, 17, 12, 17, 97, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 104, 8, 18, 1, 19, 1, 19, 1, 19, 1, 19, 0, 0, 20, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 0, 27, 0, 29, 0, 31, 13, 33, 14, 35, 15, 37, 16, 39, 17, 1, 0, 3, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 112, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 1, 41, 1, 0, 0, 0, 3, 43, 1, 0, 0, 0, 5, 45, 1, 0, 0, 0, 7, 47, 1, 0, 0, 0, 9, 49, 1, 0, 0, 0, 11, 51, 1, 0, 0, 0, 13, 53, 1, 0, 0, 0, 15, 55, 1, 0, 0, 0, 17, 59, 1, 0, 0, 0, 19, 62, 1, 0, 0, 0, 21, 64, 1, 0, 0, 0, 23, 66, 1, 0, 0, 0, 25, 68, 1, 0, 0, 0, 27, 70, 1, 0, 0, 0, 29, 74, 1, 0, 0, 0, 31, 77, 1, 0, 0, 0, 33, 89, 1, 0, 0, 0, 35, 91, 1, 0, 0, 0, 37, 103, 1, 0, 0, 0, 39, 105, 1, 0, 0, 0, 41, 42, 5, 46, 0, 0, 42, 2, 1, 0, 0, 0, 43, 44, 5, 45, 0, 0, 44, 4, 1, 0, 0, 0, 45, 46, 5, 40, 0, 0, 46, 6, 1, 0, 0, 0, 47, 48, 5, 41, 0, 0, 48, 8, 1, 0, 0, 0, 49, 50, 5, 47, 0, 0, 50, 10, 1, 0, 0, 0, 51, 52, 5, 42, 0, 0, 52, 12, 1, 0, 0, 0, 53, 54, 5, 43, 0, 0, 54, 14, 1, 0, 0, 0, 55, 56, 5, 115, 0, 0, 56, 57, 5, 117, 0, 0, 57, 58, 5, 109, 0, 0, 58, 16, 1, 0, 0, 0, 59, 60, 5, 46, 0, 0, 60, 61, 5, 46, 0, 0, 61, 18, 1, 0, 0, 0, 62, 63, 5, 44, 0, 0, 63, 20, 1, 0, 0, 0, 64, 65, 5, 91, 0, 0, 65, 22, 1, 0, 0, 0, 66, 67, 5, 93, 0, 0, 67, 24, 1, 0, 0, 0, 68, 69, 7, 0, 0, 0, 69, 26, 1, 0, 0, 0, 70, 71, 7, 1, 0, 0, 71, 28, 1, 0, 0, 0, 72, 75, 3, 27, 13, 0, 73, 75, 3, 25, 12, 0, 74, 72, 1, 0, 0, 0, 74, 73, 1, 0, 0, 0, 75, 30, 1, 0, 0, 0, 76, 78, 3, 25, 12, 0, 77, 76, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 87, 1, 0, 0, 0, 81, 83, 5, 46, 0, 0, 82, 84, 3, 25, 12, 0, 83, 82, 1, 0, 0, 0, 84, 85, 1, 0, 0, 0, 85, 83, 1, 0, 0, 0, 85, 86, 1, 0, 0, 0, 86, 88, 1, 0, 0, 0, 87, 81, 1, 0, 0, 0, 87, 88, 1, 0, 0, 0, 88, 32, 1, 0, 0, 0, 89, 90, 5, 116, 0, 0, 90, 34, 1, 0, 0, 0, 91, 95, 3, 27, 13, 0, 92, 94, 3, 29, 14, 0, 93, 92, 1, 0, 0, 0, 94, 97, 1, 0, 0, 0, 95, 93, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 36, 1, 0, 0, 0, 97, 95, 1, 0, 0, 0, 98, 104, 5, 61, 0, 0, 99, 100, 5, 62, 0, 0, 100, 104, 5, 61, 0, 0, 101, 102, 5, 60, 0, 0, 102, 104, 5, 61, 0, 0, 103, 98, 1, 0, 0, 0, 103, 99, 1, 0, 0, 0, 103, 101, 1, 0, 0, 0, 104, 38, 1, 0, 0, 0, 105, 106, 7, 2, 0, 0, 106, 107, 1, 0, 0, 0, 107, 108, 6, 19, 0, 0, 108, 40, 1, 0, 0, 0, 7, 0, 74, 79, 85, 87, 95, 103, 1, 6, 0, 0] \ No newline at end of file diff --git a/src/andromede/expression/parsing/antlr/ExprLexer.py b/src/andromede/expression/parsing/antlr/ExprLexer.py index 1ad7f368..b78b3c77 100644 --- a/src/andromede/expression/parsing/antlr/ExprLexer.py +++ b/src/andromede/expression/parsing/antlr/ExprLexer.py @@ -1,4 +1,4 @@ -# Generated from Expr.g4 by ANTLR 4.13.1 +# Generated from Expr.g4 by ANTLR 4.13.2 import sys from io import StringIO @@ -14,8 +14,8 @@ def serializedATN(): return [ 4, 0, - 16, - 103, + 17, + 109, 6, -1, 2, @@ -94,6 +94,10 @@ def serializedATN(): 18, 7, 18, + 2, + 19, + 7, + 19, 1, 0, 1, @@ -127,15 +131,19 @@ def serializedATN(): 1, 7, 1, + 7, + 1, + 7, + 1, 8, 1, 8, 1, - 9, + 8, 1, 9, 1, - 10, + 9, 1, 10, 1, @@ -152,76 +160,65 @@ def serializedATN(): 13, 1, 13, - 3, - 13, - 69, - 8, - 13, 1, 14, - 4, - 14, - 72, - 8, - 14, - 11, + 1, 14, - 12, + 3, 14, - 73, - 1, + 75, + 8, 14, 1, - 14, + 15, 4, - 14, + 15, 78, 8, - 14, + 15, 11, - 14, + 15, 12, - 14, + 15, 79, - 3, - 14, - 82, - 8, - 14, 1, 15, 1, 15, - 1, - 16, - 1, - 16, - 5, - 16, - 88, + 4, + 15, + 84, 8, - 16, - 10, - 16, + 15, + 11, + 15, 12, + 15, + 85, + 3, + 15, + 88, + 8, + 15, + 1, 16, - 91, - 9, + 1, 16, 1, 17, 1, 17, - 1, + 5, 17, - 1, + 94, + 8, 17, - 1, + 10, 17, - 3, + 12, 17, - 98, - 8, + 97, + 9, 17, 1, 18, @@ -231,9 +228,24 @@ def serializedATN(): 18, 1, 18, + 1, + 18, + 3, + 18, + 104, + 8, + 18, + 1, + 19, + 1, + 19, + 1, + 19, + 1, + 19, 0, 0, - 19, + 20, 1, 1, 3, @@ -257,13 +269,13 @@ def serializedATN(): 21, 11, 23, - 0, + 12, 25, 0, 27, 0, 29, - 12, + 0, 31, 13, 33, @@ -272,6 +284,8 @@ def serializedATN(): 15, 37, 16, + 39, + 17, 1, 0, 3, @@ -295,7 +309,7 @@ def serializedATN(): 13, 32, 32, - 106, + 112, 0, 1, 1, @@ -363,7 +377,7 @@ def serializedATN(): 0, 0, 0, - 29, + 23, 1, 0, 0, @@ -392,128 +406,128 @@ def serializedATN(): 0, 0, 0, - 1, + 0, 39, 1, 0, 0, 0, - 3, + 1, 41, 1, 0, 0, 0, - 5, + 3, 43, 1, 0, 0, 0, - 7, + 5, 45, 1, 0, 0, 0, - 9, + 7, 47, 1, 0, 0, 0, - 11, + 9, 49, 1, 0, 0, 0, - 13, + 11, 51, 1, 0, 0, 0, - 15, + 13, 53, 1, 0, 0, 0, - 17, + 15, 55, 1, 0, 0, 0, + 17, + 59, + 1, + 0, + 0, + 0, 19, - 57, + 62, 1, 0, 0, 0, 21, - 59, + 64, 1, 0, 0, 0, 23, - 62, + 66, 1, 0, 0, 0, 25, - 64, + 68, 1, 0, 0, 0, 27, - 68, + 70, 1, 0, 0, 0, 29, - 71, + 74, 1, 0, 0, 0, 31, - 83, + 77, 1, 0, 0, 0, 33, - 85, + 89, 1, 0, 0, 0, 35, - 97, + 91, 1, 0, 0, 0, 37, - 99, + 103, 1, 0, 0, 0, 39, - 40, - 5, - 46, - 0, - 0, - 40, - 2, + 105, 1, 0, 0, @@ -521,11 +535,11 @@ def serializedATN(): 41, 42, 5, - 45, + 46, 0, 0, 42, - 4, + 2, 1, 0, 0, @@ -533,11 +547,11 @@ def serializedATN(): 43, 44, 5, - 40, + 45, 0, 0, 44, - 6, + 4, 1, 0, 0, @@ -545,11 +559,11 @@ def serializedATN(): 45, 46, 5, - 41, + 40, 0, 0, 46, - 8, + 6, 1, 0, 0, @@ -557,11 +571,11 @@ def serializedATN(): 47, 48, 5, - 47, + 41, 0, 0, 48, - 10, + 8, 1, 0, 0, @@ -569,11 +583,11 @@ def serializedATN(): 49, 50, 5, - 42, + 47, 0, 0, 50, - 12, + 10, 1, 0, 0, @@ -581,11 +595,11 @@ def serializedATN(): 51, 52, 5, - 43, + 42, 0, 0, 52, - 14, + 12, 1, 0, 0, @@ -593,11 +607,11 @@ def serializedATN(): 53, 54, 5, - 91, + 43, 0, 0, 54, - 16, + 14, 1, 0, 0, @@ -605,23 +619,23 @@ def serializedATN(): 55, 56, 5, - 44, + 115, 0, 0, 56, - 18, - 1, - 0, + 57, + 5, + 117, 0, 0, 57, 58, 5, - 93, + 109, 0, 0, 58, - 20, + 16, 1, 0, 0, @@ -639,112 +653,106 @@ def serializedATN(): 0, 0, 61, - 22, + 18, 1, 0, 0, 0, 62, 63, - 7, - 0, + 5, + 44, 0, 0, 63, - 24, + 20, 1, 0, 0, 0, 64, 65, - 7, - 1, + 5, + 91, 0, 0, 65, - 26, + 22, 1, 0, 0, 0, 66, - 69, - 3, - 25, - 12, - 0, 67, - 69, - 3, - 23, - 11, + 5, + 93, 0, - 68, - 66, + 0, + 67, + 24, 1, 0, 0, 0, 68, - 67, - 1, + 69, + 7, 0, 0, 0, 69, - 28, + 26, 1, 0, 0, 0, 70, - 72, - 3, - 23, - 11, - 0, 71, - 70, - 1, - 0, - 0, - 0, - 72, - 73, + 7, 1, 0, 0, - 0, - 73, 71, + 28, 1, 0, 0, 0, + 72, + 75, + 3, + 27, + 13, + 0, 73, + 75, + 3, + 25, + 12, + 0, 74, + 72, 1, 0, 0, 0, 74, - 81, + 73, 1, 0, 0, 0, 75, - 77, - 5, - 46, + 30, + 1, + 0, 0, 0, 76, 78, 3, - 23, - 11, + 25, + 12, 0, 77, 76, @@ -771,75 +779,75 @@ def serializedATN(): 0, 0, 80, - 82, + 87, 1, 0, 0, 0, 81, - 75, - 1, + 83, + 5, + 46, 0, 0, + 82, + 84, + 3, + 25, + 12, 0, - 81, + 83, 82, 1, 0, 0, 0, - 82, - 30, + 84, + 85, 1, 0, 0, 0, + 85, 83, - 84, - 5, - 116, - 0, - 0, - 84, - 32, 1, 0, 0, 0, 85, - 89, - 3, - 25, - 12, + 86, + 1, + 0, + 0, 0, 86, 88, - 3, - 27, - 13, + 1, + 0, + 0, 0, 87, - 86, + 81, 1, 0, 0, 0, + 87, 88, - 91, 1, 0, 0, 0, - 89, - 87, + 88, + 32, 1, 0, 0, 0, 89, 90, - 1, - 0, + 5, + 116, 0, 0, 90, @@ -849,97 +857,139 @@ def serializedATN(): 0, 0, 91, - 89, + 95, + 3, + 27, + 13, + 0, + 92, + 94, + 3, + 29, + 14, + 0, + 93, + 92, + 1, + 0, + 0, + 0, + 94, + 97, + 1, + 0, + 0, + 0, + 95, + 93, + 1, + 0, + 0, + 0, + 95, + 96, + 1, + 0, + 0, + 0, + 96, + 36, + 1, + 0, + 0, + 0, + 97, + 95, 1, 0, 0, 0, - 92, 98, + 104, 5, 61, 0, 0, - 93, - 94, + 99, + 100, 5, 62, 0, 0, - 94, - 98, + 100, + 104, 5, 61, 0, 0, - 95, - 96, + 101, + 102, 5, 60, 0, 0, - 96, - 98, + 102, + 104, 5, 61, 0, 0, - 97, - 92, + 103, + 98, 1, 0, 0, 0, - 97, - 93, + 103, + 99, 1, 0, 0, 0, - 97, - 95, + 103, + 101, 1, 0, 0, 0, - 98, - 36, + 104, + 38, 1, 0, 0, 0, - 99, - 100, + 105, + 106, 7, 2, 0, 0, - 100, - 101, + 106, + 107, 1, 0, 0, 0, - 101, - 102, + 107, + 108, 6, - 18, + 19, 0, 0, - 102, - 38, + 108, + 40, 1, 0, 0, 0, 7, 0, - 68, - 73, + 74, 79, - 81, - 89, - 97, + 85, + 87, + 95, + 103, 1, 6, 0, @@ -963,11 +1013,12 @@ class ExprLexer(Lexer): T__8 = 9 T__9 = 10 T__10 = 11 - NUMBER = 12 - TIME = 13 - IDENTIFIER = 14 - COMPARISON = 15 - WS = 16 + T__11 = 12 + NUMBER = 13 + TIME = 14 + IDENTIFIER = 15 + COMPARISON = 16 + WS = 17 channelNames = ["DEFAULT_TOKEN_CHANNEL", "HIDDEN"] @@ -982,10 +1033,11 @@ class ExprLexer(Lexer): "'/'", "'*'", "'+'", - "'['", + "'sum'", + "'..'", "','", + "'['", "']'", - "'..'", "'t'", ] @@ -1003,6 +1055,7 @@ class ExprLexer(Lexer): "T__8", "T__9", "T__10", + "T__11", "DIGIT", "CHAR", "CHAR_OR_DIGIT", @@ -1017,7 +1070,7 @@ class ExprLexer(Lexer): def __init__(self, input=None, output: TextIO = sys.stdout): super().__init__(input, output) - self.checkVersion("4.13.1") + self.checkVersion("4.13.2") self._interp = LexerATNSimulator( self, self.atn, self.decisionsToDFA, PredictionContextCache() ) diff --git a/src/andromede/expression/parsing/antlr/ExprLexer.tokens b/src/andromede/expression/parsing/antlr/ExprLexer.tokens index 9401c83a..86857744 100644 --- a/src/andromede/expression/parsing/antlr/ExprLexer.tokens +++ b/src/andromede/expression/parsing/antlr/ExprLexer.tokens @@ -9,11 +9,12 @@ T__7=8 T__8=9 T__9=10 T__10=11 -NUMBER=12 -TIME=13 -IDENTIFIER=14 -COMPARISON=15 -WS=16 +T__11=12 +NUMBER=13 +TIME=14 +IDENTIFIER=15 +COMPARISON=16 +WS=17 '.'=1 '-'=2 '('=3 @@ -21,8 +22,9 @@ WS=16 '/'=5 '*'=6 '+'=7 -'['=8 -','=9 -']'=10 -'..'=11 -'t'=13 +'sum'=8 +'..'=9 +','=10 +'['=11 +']'=12 +'t'=14 diff --git a/src/andromede/expression/parsing/antlr/ExprParser.py b/src/andromede/expression/parsing/antlr/ExprParser.py index 8f312fe9..085c5390 100644 --- a/src/andromede/expression/parsing/antlr/ExprParser.py +++ b/src/andromede/expression/parsing/antlr/ExprParser.py @@ -1,4 +1,4 @@ -# Generated from Expr.g4 by ANTLR 4.13.1 +# Generated from Expr.g4 by ANTLR 4.13.2 # encoding: utf-8 import sys from io import StringIO @@ -15,8 +15,8 @@ def serializedATN(): return [ 4, 1, - 16, - 131, + 17, + 117, 2, 0, 7, @@ -89,18 +89,6 @@ def serializedATN(): 1, 1, 1, - 5, - 1, - 37, - 8, - 1, - 10, - 1, - 12, - 1, - 40, - 9, - 1, 1, 1, 1, @@ -115,26 +103,6 @@ def serializedATN(): 1, 1, 1, - 5, - 1, - 49, - 8, - 1, - 10, - 1, - 12, - 1, - 52, - 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 1, 1, @@ -161,7 +129,7 @@ def serializedATN(): 1, 3, 1, - 70, + 56, 8, 1, 1, @@ -184,14 +152,14 @@ def serializedATN(): 1, 5, 1, - 81, + 67, 8, 1, 10, 1, 12, 1, - 84, + 70, 9, 1, 1, @@ -200,7 +168,7 @@ def serializedATN(): 2, 3, 2, - 88, + 74, 8, 2, 1, @@ -209,7 +177,7 @@ def serializedATN(): 3, 3, 3, - 92, + 78, 8, 3, 1, @@ -230,7 +198,7 @@ def serializedATN(): 4, 3, 4, - 102, + 88, 8, 4, 1, @@ -247,14 +215,14 @@ def serializedATN(): 4, 5, 4, - 110, + 96, 8, 4, 10, 4, 12, 4, - 113, + 99, 9, 4, 1, @@ -271,7 +239,7 @@ def serializedATN(): 5, 3, 5, - 121, + 107, 8, 5, 1, @@ -282,14 +250,14 @@ def serializedATN(): 5, 5, 5, - 126, + 112, 8, 5, 10, 5, 12, 5, - 129, + 115, 9, 5, 1, @@ -318,7 +286,7 @@ def serializedATN(): 2, 7, 7, - 144, + 128, 0, 12, 1, @@ -326,31 +294,31 @@ def serializedATN(): 0, 0, 2, - 69, + 55, 1, 0, 0, 0, 4, - 87, + 73, 1, 0, 0, 0, 6, - 89, + 75, 1, 0, 0, 0, 8, - 101, + 87, 1, 0, 0, 0, 10, - 120, + 106, 1, 0, 0, @@ -380,7 +348,7 @@ def serializedATN(): -1, 0, 16, - 70, + 56, 3, 4, 2, @@ -388,7 +356,7 @@ def serializedATN(): 17, 18, 5, - 14, + 15, 0, 0, 18, @@ -398,9 +366,9 @@ def serializedATN(): 0, 0, 19, - 70, + 56, 5, - 14, + 15, 0, 0, 20, @@ -410,7 +378,7 @@ def serializedATN(): 0, 0, 21, - 70, + 56, 3, 2, 1, @@ -434,7 +402,7 @@ def serializedATN(): 0, 0, 25, - 70, + 56, 1, 0, 0, @@ -442,7 +410,7 @@ def serializedATN(): 26, 27, 5, - 14, + 8, 0, 0, 27, @@ -464,7 +432,7 @@ def serializedATN(): 0, 0, 30, - 70, + 56, 1, 0, 0, @@ -472,17 +440,17 @@ def serializedATN(): 31, 32, 5, - 14, + 8, 0, 0, 32, 33, 5, - 8, + 3, 0, 0, 33, - 38, + 34, 3, 6, 3, @@ -494,708 +462,610 @@ def serializedATN(): 0, 0, 35, - 37, + 36, 3, 6, 3, 0, 36, - 34, - 1, - 0, - 0, - 0, 37, - 40, - 1, - 0, + 5, + 10, 0, 0, + 37, 38, - 36, + 3, + 2, 1, 0, - 0, - 0, 38, 39, - 1, - 0, + 5, + 4, 0, 0, 39, - 41, + 56, 1, 0, 0, 0, 40, - 38, - 1, - 0, + 41, + 5, + 15, 0, 0, 41, 42, 5, - 10, + 3, 0, 0, 42, - 70, + 43, + 3, + 2, 1, 0, - 0, - 0, 43, 44, 5, - 14, + 4, 0, 0, 44, - 45, - 5, - 8, - 0, - 0, - 45, - 50, - 3, - 2, + 56, 1, 0, - 46, - 47, - 5, - 9, 0, 0, - 47, - 49, - 3, - 2, - 1, - 0, - 48, + 45, 46, - 1, - 0, - 0, - 0, - 49, - 52, - 1, - 0, - 0, - 0, - 50, - 48, - 1, - 0, - 0, - 0, - 50, - 51, - 1, - 0, - 0, - 0, - 51, - 53, - 1, - 0, - 0, - 0, - 52, - 50, - 1, - 0, - 0, - 0, - 53, - 54, - 5, - 10, - 0, - 0, - 54, - 70, - 1, - 0, - 0, - 0, - 55, - 56, - 5, - 14, - 0, - 0, - 56, - 57, 5, - 8, - 0, + 15, 0, - 57, - 58, - 3, - 6, - 3, 0, - 58, - 59, + 46, + 47, 5, 11, 0, 0, - 59, - 60, + 47, + 48, 3, 6, 3, 0, - 60, - 61, + 48, + 49, 5, - 10, + 12, 0, 0, - 61, - 70, + 49, + 56, 1, 0, 0, 0, - 62, - 63, - 5, - 14, - 0, - 0, - 63, - 64, + 50, + 51, 5, - 8, + 15, 0, 0, - 64, - 65, - 3, - 2, - 1, - 0, - 65, - 66, + 51, + 52, 5, 11, 0, 0, - 66, - 67, + 52, + 53, 3, 2, 1, 0, - 67, - 68, + 53, + 54, 5, - 10, + 12, 0, 0, - 68, - 70, + 54, + 56, 1, 0, 0, 0, - 69, + 55, 15, 1, 0, 0, 0, - 69, + 55, 17, 1, 0, 0, 0, - 69, + 55, 20, 1, 0, 0, 0, - 69, + 55, 22, 1, 0, 0, 0, - 69, + 55, 26, 1, 0, 0, 0, - 69, + 55, 31, 1, 0, 0, 0, - 69, - 43, + 55, + 40, 1, 0, 0, 0, - 69, 55, + 45, 1, 0, 0, 0, - 69, - 62, + 55, + 50, 1, 0, 0, 0, - 70, - 82, + 56, + 68, 1, 0, 0, 0, - 71, - 72, + 57, + 58, 10, 8, 0, 0, - 72, - 73, + 58, + 59, 7, 0, 0, 0, - 73, - 81, + 59, + 67, 3, 2, 1, 9, - 74, - 75, + 60, + 61, 10, 7, 0, 0, - 75, - 76, + 61, + 62, 7, 1, 0, 0, - 76, - 81, + 62, + 67, 3, 2, 1, 8, - 77, - 78, + 63, + 64, 10, 6, 0, 0, - 78, - 79, + 64, + 65, 5, - 15, + 16, 0, 0, - 79, - 81, + 65, + 67, 3, 2, 1, 7, - 80, - 71, + 66, + 57, 1, 0, 0, 0, - 80, - 74, + 66, + 60, 1, 0, 0, 0, - 80, - 77, + 66, + 63, 1, 0, 0, 0, - 81, - 84, + 67, + 70, 1, 0, 0, 0, - 82, - 80, + 68, + 66, 1, 0, 0, 0, - 82, - 83, + 68, + 69, 1, 0, 0, 0, - 83, + 69, 3, 1, 0, 0, 0, - 84, - 82, + 70, + 68, 1, 0, 0, 0, - 85, - 88, + 71, + 74, 5, - 12, + 13, 0, 0, - 86, - 88, + 72, + 74, 5, - 14, + 15, 0, 0, - 87, - 85, + 73, + 71, 1, 0, 0, 0, - 87, - 86, + 73, + 72, 1, 0, 0, 0, - 88, + 74, 5, 1, 0, 0, 0, - 89, - 91, + 75, + 77, 5, - 13, + 14, 0, 0, - 90, - 92, + 76, + 78, 3, 8, 4, 0, - 91, - 90, + 77, + 76, 1, 0, 0, 0, - 91, - 92, + 77, + 78, 1, 0, 0, 0, - 92, + 78, 7, 1, 0, 0, 0, - 93, - 94, + 79, + 80, 6, 4, -1, 0, - 94, - 95, + 80, + 81, 7, 1, 0, 0, - 95, - 102, + 81, + 88, 3, 4, 2, 0, - 96, - 97, + 82, + 83, 7, 1, 0, 0, - 97, - 98, + 83, + 84, 5, 3, 0, 0, - 98, - 99, + 84, + 85, 3, 2, 1, 0, - 99, - 100, + 85, + 86, 5, 4, 0, 0, - 100, - 102, + 86, + 88, 1, 0, 0, 0, - 101, - 93, + 87, + 79, 1, 0, 0, 0, - 101, - 96, + 87, + 82, 1, 0, 0, 0, - 102, - 111, + 88, + 97, 1, 0, 0, 0, - 103, - 104, + 89, + 90, 10, 4, 0, 0, - 104, - 105, + 90, + 91, 7, 0, 0, 0, - 105, - 110, + 91, + 96, 3, 10, 5, 0, - 106, - 107, + 92, + 93, 10, 3, 0, 0, - 107, - 108, + 93, + 94, 7, 1, 0, 0, - 108, - 110, + 94, + 96, 3, 10, 5, 0, - 109, - 103, + 95, + 89, 1, 0, 0, 0, - 109, - 106, + 95, + 92, 1, 0, 0, 0, - 110, - 113, + 96, + 99, 1, 0, 0, 0, - 111, - 109, + 97, + 95, 1, 0, 0, 0, - 111, - 112, + 97, + 98, 1, 0, 0, 0, - 112, + 98, 9, 1, 0, 0, 0, - 113, - 111, + 99, + 97, 1, 0, 0, 0, - 114, - 115, + 100, + 101, 6, 5, -1, 0, - 115, - 116, + 101, + 102, 5, 3, 0, 0, - 116, - 117, + 102, + 103, 3, 2, 1, 0, - 117, - 118, + 103, + 104, 5, 4, 0, 0, - 118, - 121, + 104, + 107, 1, 0, 0, 0, - 119, - 121, + 105, + 107, 3, 4, 2, 0, - 120, - 114, + 106, + 100, 1, 0, 0, 0, - 120, - 119, + 106, + 105, 1, 0, 0, 0, - 121, - 127, + 107, + 113, 1, 0, 0, 0, - 122, - 123, + 108, + 109, 10, 3, 0, 0, - 123, - 124, + 109, + 110, 7, 0, 0, 0, - 124, - 126, + 110, + 112, 3, 10, 5, 4, - 125, - 122, + 111, + 108, 1, 0, 0, 0, - 126, - 129, + 112, + 115, 1, 0, 0, 0, - 127, - 125, + 113, + 111, 1, 0, 0, 0, - 127, - 128, + 113, + 114, 1, 0, 0, 0, - 128, + 114, 11, 1, 0, 0, 0, - 129, - 127, + 115, + 113, 1, 0, 0, 0, - 12, - 38, - 50, - 69, - 80, - 82, + 10, + 55, + 66, + 68, + 73, + 77, 87, - 91, - 101, - 109, - 111, - 120, - 127, + 95, + 97, + 106, + 113, ] @@ -1217,10 +1087,11 @@ class ExprParser(Parser): "'/'", "'*'", "'+'", - "'['", + "'sum'", + "'..'", "','", + "'['", "']'", - "'..'", "", "'t'", ] @@ -1238,6 +1109,7 @@ class ExprParser(Parser): "", "", "", + "", "NUMBER", "TIME", "IDENTIFIER", @@ -1266,15 +1138,16 @@ class ExprParser(Parser): T__8 = 9 T__9 = 10 T__10 = 11 - NUMBER = 12 - TIME = 13 - IDENTIFIER = 14 - COMPARISON = 15 - WS = 16 + T__11 = 12 + NUMBER = 13 + TIME = 14 + IDENTIFIER = 15 + COMPARISON = 16 + WS = 17 def __init__(self, input: TokenStream, output: TextIO = sys.stdout): super().__init__(input, output) - self.checkVersion("4.13.1") + self.checkVersion("4.13.2") self._interp = ParserATNSimulator( self, self.atn, self.decisionsToDFA, self.sharedContextCache ) @@ -1336,6 +1209,30 @@ def getRuleIndex(self): def copyFrom(self, ctx: ParserRuleContext): super().copyFrom(ctx) + class TimeSumContext(ExprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.ExprContext + super().__init__(parser) + self.from_ = None # ShiftContext + self.to = None # ShiftContext + self.copyFrom(ctx) + + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) + + def shift(self, i: int = None): + if i is None: + return self.getTypedRuleContexts(ExprParser.ShiftContext) + else: + return self.getTypedRuleContext(ExprParser.ShiftContext, i) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitTimeSum"): + return visitor.visitTimeSum(self) + else: + return visitor.visitChildren(self) + class NegationContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext @@ -1394,11 +1291,8 @@ def __init__( def IDENTIFIER(self): return self.getToken(ExprParser.IDENTIFIER, 0) - def expr(self, i: int = None): - if i is None: - return self.getTypedRuleContexts(ExprParser.ExprContext) - else: - return self.getTypedRuleContext(ExprParser.ExprContext, i) + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) def accept(self, visitor: ParseTreeVisitor): if hasattr(visitor, "visitTimeIndex"): @@ -1428,6 +1322,22 @@ def accept(self, visitor: ParseTreeVisitor): else: return visitor.visitChildren(self) + class AllTimeSumContext(ExprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.ExprContext + super().__init__(parser) + self.copyFrom(ctx) + + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitAllTimeSum"): + return visitor.visitAllTimeSum(self) + else: + return visitor.visitChildren(self) + class TimeShiftContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext @@ -1438,11 +1348,8 @@ def __init__( def IDENTIFIER(self): return self.getToken(ExprParser.IDENTIFIER, 0) - def shift(self, i: int = None): - if i is None: - return self.getTypedRuleContexts(ExprParser.ShiftContext) - else: - return self.getTypedRuleContext(ExprParser.ShiftContext, i) + def shift(self): + return self.getTypedRuleContext(ExprParser.ShiftContext, 0) def accept(self, visitor: ParseTreeVisitor): if hasattr(visitor, "visitTimeShift"): @@ -1489,30 +1396,6 @@ def accept(self, visitor: ParseTreeVisitor): else: return visitor.visitChildren(self) - class TimeShiftRangeContext(ExprContext): - def __init__( - self, parser, ctx: ParserRuleContext - ): # actually a ExprParser.ExprContext - super().__init__(parser) - self.shift1 = None # ShiftContext - self.shift2 = None # ShiftContext - self.copyFrom(ctx) - - def IDENTIFIER(self): - return self.getToken(ExprParser.IDENTIFIER, 0) - - def shift(self, i: int = None): - if i is None: - return self.getTypedRuleContexts(ExprParser.ShiftContext) - else: - return self.getTypedRuleContext(ExprParser.ShiftContext, i) - - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitTimeShiftRange"): - return visitor.visitTimeShiftRange(self) - else: - return visitor.visitChildren(self) - class PortFieldContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext @@ -1552,28 +1435,6 @@ def accept(self, visitor: ParseTreeVisitor): else: return visitor.visitChildren(self) - class TimeRangeContext(ExprContext): - def __init__( - self, parser, ctx: ParserRuleContext - ): # actually a ExprParser.ExprContext - super().__init__(parser) - self.copyFrom(ctx) - - def IDENTIFIER(self): - return self.getToken(ExprParser.IDENTIFIER, 0) - - def expr(self, i: int = None): - if i is None: - return self.getTypedRuleContexts(ExprParser.ExprContext) - else: - return self.getTypedRuleContext(ExprParser.ExprContext, i) - - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitTimeRange"): - return visitor.visitTimeRange(self) - else: - return visitor.visitChildren(self) - def expr(self, _p: int = 0): _parentctx = self._ctx _parentState = self.state @@ -1584,9 +1445,9 @@ def expr(self, _p: int = 0): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 69 + self.state = 55 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input, 2, self._ctx) + la_ = self._interp.adaptivePredict(self._input, 0, self._ctx) if la_ == 1: localctx = ExprParser.UnsignedAtomContext(self, localctx) self._ctx = localctx @@ -1631,11 +1492,11 @@ def expr(self, _p: int = 0): pass elif la_ == 5: - localctx = ExprParser.FunctionContext(self, localctx) + localctx = ExprParser.AllTimeSumContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 26 - self.match(ExprParser.IDENTIFIER) + self.match(ExprParser.T__7) self.state = 27 self.match(ExprParser.T__2) self.state = 28 @@ -1645,105 +1506,81 @@ def expr(self, _p: int = 0): pass elif la_ == 6: - localctx = ExprParser.TimeShiftContext(self, localctx) + localctx = ExprParser.TimeSumContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 31 - self.match(ExprParser.IDENTIFIER) - self.state = 32 self.match(ExprParser.T__7) + self.state = 32 + self.match(ExprParser.T__2) self.state = 33 - self.shift() - self.state = 38 - self._errHandler.sync(self) - _la = self._input.LA(1) - while _la == 9: - self.state = 34 - self.match(ExprParser.T__8) - self.state = 35 - self.shift() - self.state = 40 - self._errHandler.sync(self) - _la = self._input.LA(1) - - self.state = 41 + localctx.from_ = self.shift() + self.state = 34 + self.match(ExprParser.T__8) + self.state = 35 + localctx.to = self.shift() + self.state = 36 self.match(ExprParser.T__9) + self.state = 37 + self.expr(0) + self.state = 38 + self.match(ExprParser.T__3) pass elif la_ == 7: - localctx = ExprParser.TimeIndexContext(self, localctx) + localctx = ExprParser.FunctionContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 43 + self.state = 40 self.match(ExprParser.IDENTIFIER) - self.state = 44 - self.match(ExprParser.T__7) - self.state = 45 + self.state = 41 + self.match(ExprParser.T__2) + self.state = 42 self.expr(0) - self.state = 50 - self._errHandler.sync(self) - _la = self._input.LA(1) - while _la == 9: - self.state = 46 - self.match(ExprParser.T__8) - self.state = 47 - self.expr(0) - self.state = 52 - self._errHandler.sync(self) - _la = self._input.LA(1) - - self.state = 53 - self.match(ExprParser.T__9) + self.state = 43 + self.match(ExprParser.T__3) pass elif la_ == 8: - localctx = ExprParser.TimeShiftRangeContext(self, localctx) + localctx = ExprParser.TimeShiftContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 55 + self.state = 45 self.match(ExprParser.IDENTIFIER) - self.state = 56 - self.match(ExprParser.T__7) - self.state = 57 - localctx.shift1 = self.shift() - self.state = 58 + self.state = 46 self.match(ExprParser.T__10) - self.state = 59 - localctx.shift2 = self.shift() - self.state = 60 - self.match(ExprParser.T__9) + self.state = 47 + self.shift() + self.state = 48 + self.match(ExprParser.T__11) pass elif la_ == 9: - localctx = ExprParser.TimeRangeContext(self, localctx) + localctx = ExprParser.TimeIndexContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 62 + self.state = 50 self.match(ExprParser.IDENTIFIER) - self.state = 63 - self.match(ExprParser.T__7) - self.state = 64 - self.expr(0) - self.state = 65 + self.state = 51 self.match(ExprParser.T__10) - self.state = 66 + self.state = 52 self.expr(0) - self.state = 67 - self.match(ExprParser.T__9) + self.state = 53 + self.match(ExprParser.T__11) pass self._ctx.stop = self._input.LT(-1) - self.state = 82 + self.state = 68 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 4, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 2, self._ctx) while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: if _alt == 1: if self._parseListeners is not None: self.triggerExitRuleEvent() _prevctx = localctx - self.state = 80 + self.state = 66 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input, 3, self._ctx) + la_ = self._interp.adaptivePredict(self._input, 1, self._ctx) if la_ == 1: localctx = ExprParser.MuldivContext( self, ExprParser.ExprContext(self, _parentctx, _parentState) @@ -1751,14 +1588,14 @@ def expr(self, _p: int = 0): self.pushNewRecursionContext( localctx, _startState, self.RULE_expr ) - self.state = 71 + self.state = 57 if not self.precpred(self._ctx, 8): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( self, "self.precpred(self._ctx, 8)" ) - self.state = 72 + self.state = 58 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 5 or _la == 6): @@ -1766,7 +1603,7 @@ def expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 73 + self.state = 59 self.expr(9) pass @@ -1777,14 +1614,14 @@ def expr(self, _p: int = 0): self.pushNewRecursionContext( localctx, _startState, self.RULE_expr ) - self.state = 74 + self.state = 60 if not self.precpred(self._ctx, 7): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( self, "self.precpred(self._ctx, 7)" ) - self.state = 75 + self.state = 61 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 2 or _la == 7): @@ -1792,7 +1629,7 @@ def expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 76 + self.state = 62 self.expr(8) pass @@ -1803,22 +1640,22 @@ def expr(self, _p: int = 0): self.pushNewRecursionContext( localctx, _startState, self.RULE_expr ) - self.state = 77 + self.state = 63 if not self.precpred(self._ctx, 6): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( self, "self.precpred(self._ctx, 6)" ) - self.state = 78 + self.state = 64 self.match(ExprParser.COMPARISON) - self.state = 79 + self.state = 65 self.expr(7) pass - self.state = 84 + self.state = 70 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 4, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 2, self._ctx) except RecognitionException as re: localctx.exception = re @@ -1879,19 +1716,19 @@ def atom(self): localctx = ExprParser.AtomContext(self, self._ctx, self.state) self.enterRule(localctx, 4, self.RULE_atom) try: - self.state = 87 + self.state = 73 self._errHandler.sync(self) token = self._input.LA(1) - if token in [12]: + if token in [13]: localctx = ExprParser.NumberContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 85 + self.state = 71 self.match(ExprParser.NUMBER) pass - elif token in [14]: + elif token in [15]: localctx = ExprParser.IdentifierContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 86 + self.state = 72 self.match(ExprParser.IDENTIFIER) pass else: @@ -1935,13 +1772,13 @@ def shift(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 89 + self.state = 75 self.match(ExprParser.TIME) - self.state = 91 + self.state = 77 self._errHandler.sync(self) _la = self._input.LA(1) if _la == 2 or _la == 7: - self.state = 90 + self.state = 76 self.shift_expr(0) except RecognitionException as re: @@ -2051,15 +1888,15 @@ def shift_expr(self, _p: int = 0): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 101 + self.state = 87 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input, 7, self._ctx) + la_ = self._interp.adaptivePredict(self._input, 5, self._ctx) if la_ == 1: localctx = ExprParser.SignedAtomContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 94 + self.state = 80 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 2 or _la == 7): @@ -2067,7 +1904,7 @@ def shift_expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 95 + self.state = 81 self.atom() pass @@ -2075,7 +1912,7 @@ def shift_expr(self, _p: int = 0): localctx = ExprParser.SignedExpressionContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 96 + self.state = 82 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 2 or _la == 7): @@ -2083,26 +1920,26 @@ def shift_expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 97 + self.state = 83 self.match(ExprParser.T__2) - self.state = 98 + self.state = 84 self.expr(0) - self.state = 99 + self.state = 85 self.match(ExprParser.T__3) pass self._ctx.stop = self._input.LT(-1) - self.state = 111 + self.state = 97 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 7, self._ctx) while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: if _alt == 1: if self._parseListeners is not None: self.triggerExitRuleEvent() _prevctx = localctx - self.state = 109 + self.state = 95 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input, 8, self._ctx) + la_ = self._interp.adaptivePredict(self._input, 6, self._ctx) if la_ == 1: localctx = ExprParser.ShiftMuldivContext( self, @@ -2113,14 +1950,14 @@ def shift_expr(self, _p: int = 0): self.pushNewRecursionContext( localctx, _startState, self.RULE_shift_expr ) - self.state = 103 + self.state = 89 if not self.precpred(self._ctx, 4): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( self, "self.precpred(self._ctx, 4)" ) - self.state = 104 + self.state = 90 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 5 or _la == 6): @@ -2128,7 +1965,7 @@ def shift_expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 105 + self.state = 91 self.right_expr(0) pass @@ -2142,14 +1979,14 @@ def shift_expr(self, _p: int = 0): self.pushNewRecursionContext( localctx, _startState, self.RULE_shift_expr ) - self.state = 106 + self.state = 92 if not self.precpred(self._ctx, 3): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( self, "self.precpred(self._ctx, 3)" ) - self.state = 107 + self.state = 93 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 2 or _la == 7): @@ -2157,13 +1994,13 @@ def shift_expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 108 + self.state = 94 self.right_expr(0) pass - self.state = 113 + self.state = 99 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 7, self._ctx) except RecognitionException as re: localctx.exception = re @@ -2250,7 +2087,7 @@ def right_expr(self, _p: int = 0): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 120 + self.state = 106 self._errHandler.sync(self) token = self._input.LA(1) if token in [3]: @@ -2258,27 +2095,27 @@ def right_expr(self, _p: int = 0): self._ctx = localctx _prevctx = localctx - self.state = 115 + self.state = 101 self.match(ExprParser.T__2) - self.state = 116 + self.state = 102 self.expr(0) - self.state = 117 + self.state = 103 self.match(ExprParser.T__3) pass - elif token in [12, 14]: + elif token in [13, 15]: localctx = ExprParser.RightAtomContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 119 + self.state = 105 self.atom() pass else: raise NoViableAltException(self) self._ctx.stop = self._input.LT(-1) - self.state = 127 + self.state = 113 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 11, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: if _alt == 1: if self._parseListeners is not None: @@ -2291,14 +2128,14 @@ def right_expr(self, _p: int = 0): self.pushNewRecursionContext( localctx, _startState, self.RULE_right_expr ) - self.state = 122 + self.state = 108 if not self.precpred(self._ctx, 3): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( self, "self.precpred(self._ctx, 3)" ) - self.state = 123 + self.state = 109 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not (_la == 5 or _la == 6): @@ -2306,11 +2143,11 @@ def right_expr(self, _p: int = 0): else: self._errHandler.reportMatch(self) self.consume() - self.state = 124 + self.state = 110 self.right_expr(4) - self.state = 129 + self.state = 115 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 11, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) except RecognitionException as re: localctx.exception = re diff --git a/src/andromede/expression/parsing/antlr/ExprVisitor.py b/src/andromede/expression/parsing/antlr/ExprVisitor.py index 0e924349..db080cbf 100644 --- a/src/andromede/expression/parsing/antlr/ExprVisitor.py +++ b/src/andromede/expression/parsing/antlr/ExprVisitor.py @@ -1,4 +1,4 @@ -# Generated from Expr.g4 by ANTLR 4.13.1 +# Generated from Expr.g4 by ANTLR 4.13.2 from antlr4 import * if "." in __name__: @@ -14,6 +14,10 @@ class ExprVisitor(ParseTreeVisitor): def visitFullexpr(self, ctx: ExprParser.FullexprContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#timeSum. + def visitTimeSum(self, ctx: ExprParser.TimeSumContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#negation. def visitNegation(self, ctx: ExprParser.NegationContext): return self.visitChildren(ctx) @@ -34,6 +38,10 @@ def visitTimeIndex(self, ctx: ExprParser.TimeIndexContext): def visitComparison(self, ctx: ExprParser.ComparisonContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#allTimeSum. + def visitAllTimeSum(self, ctx: ExprParser.AllTimeSumContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#timeShift. def visitTimeShift(self, ctx: ExprParser.TimeShiftContext): return self.visitChildren(ctx) @@ -46,10 +54,6 @@ def visitFunction(self, ctx: ExprParser.FunctionContext): def visitAddsub(self, ctx: ExprParser.AddsubContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#timeShiftRange. - def visitTimeShiftRange(self, ctx: ExprParser.TimeShiftRangeContext): - return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#portField. def visitPortField(self, ctx: ExprParser.PortFieldContext): return self.visitChildren(ctx) @@ -58,10 +62,6 @@ def visitPortField(self, ctx: ExprParser.PortFieldContext): def visitMuldiv(self, ctx: ExprParser.MuldivContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#timeRange. - def visitTimeRange(self, ctx: ExprParser.TimeRangeContext): - return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#number. def visitNumber(self, ctx: ExprParser.NumberContext): return self.visitChildren(ctx) diff --git a/src/andromede/expression/parsing/parse_expression.py b/src/andromede/expression/parsing/parse_expression.py index e96a70f1..2bd9d3fd 100644 --- a/src/andromede/expression/parsing/parse_expression.py +++ b/src/andromede/expression/parsing/parse_expression.py @@ -17,12 +17,7 @@ from andromede.expression import ExpressionNode, literal, param, var from andromede.expression.equality import expressions_equal -from andromede.expression.expression import ( - Comparator, - ComparisonNode, - ExpressionRange, - PortFieldNode, -) +from andromede.expression.expression import Comparator, ComparisonNode, PortFieldNode from andromede.expression.parsing.antlr.ExprLexer import ExprLexer from andromede.expression.parsing.antlr.ExprParser import ExprParser from andromede.expression.parsing.antlr.ExprVisitor import ExprVisitor @@ -125,31 +120,27 @@ def visitComparison(self, ctx: ExprParser.ComparisonContext) -> ExpressionNode: # Visit a parse tree produced by ExprParser#timeShift. def visitTimeIndex(self, ctx: ExprParser.TimeIndexContext) -> ExpressionNode: - shifted_expr = self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore - time_shifts = [e.accept(self) for e in ctx.expr()] # type: ignore - return shifted_expr.eval(time_shifts) - - # Visit a parse tree produced by ExprParser#rangeTimeShift. - def visitTimeRange(self, ctx: ExprParser.TimeRangeContext) -> ExpressionNode: - shifted_expr = self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore - expressions = [e.accept(self) for e in ctx.expr()] # type: ignore - return shifted_expr.eval(ExpressionRange(expressions[0], expressions[1])) + expr = self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore + eval_time = ctx.expr().accept(self) # type: ignore + return expr.eval(eval_time) def visitTimeShift(self, ctx: ExprParser.TimeShiftContext) -> ExpressionNode: shifted_expr = self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore - time_shifts = [s.accept(self) for s in ctx.shift()] # type: ignore + time_shift = ctx.shift().accept(self) # type: ignore # specifics for x[t] ... - if len(time_shifts) == 1 and expressions_equal(time_shifts[0], literal(0)): + if expressions_equal(time_shift, literal(0)): return shifted_expr - return shifted_expr.shift(time_shifts) + return shifted_expr.shift(time_shift) - def visitTimeShiftRange( - self, ctx: ExprParser.TimeShiftRangeContext - ) -> ExpressionNode: - shifted_expr = self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore - shift1 = ctx.shift1.accept(self) # type: ignore - shift2 = ctx.shift2.accept(self) # type: ignore - return shifted_expr.shift(ExpressionRange(shift1, shift2)) + def visitTimeSum(self, ctx: ExprParser.TimeSumContext) -> ExpressionNode: + shifted_expr = ctx.expr().accept(self) # type: ignore + from_shift = ctx.from_.accept(self) # type: ignore + to_shift = ctx.to.accept(self) # type: ignore + return shifted_expr.time_sum(from_shift, to_shift) + + def visitAllTimeSum(self, ctx: ExprParser.AllTimeSumContext) -> ExpressionNode: + shifted_expr = ctx.expr().accept(self) # type: ignore + return shifted_expr.time_sum() # Visit a parse tree produced by ExprParser#function. def visitFunction(self, ctx: ExprParser.FunctionContext) -> ExpressionNode: @@ -228,7 +219,6 @@ def visitRightAtom(self, ctx: ExprParser.RightAtomContext) -> ExpressionNode: _FUNCTIONS = { - "sum": ExpressionNode.sum, "sum_connections": ExpressionNode.sum_connections, "expec": ExpressionNode.expec, } diff --git a/src/andromede/expression/port_resolver.py b/src/andromede/expression/port_resolver.py index 6f333408..bc7374bd 100644 --- a/src/andromede/expression/port_resolver.py +++ b/src/andromede/expression/port_resolver.py @@ -10,19 +10,21 @@ # # This file is part of the Antares project. -from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Dict, List from andromede.expression import CopyVisitor, sum_expressions, visit from andromede.expression.expression import ( - AdditionNode, ExpressionNode, - LiteralNode, PortFieldAggregatorNode, PortFieldNode, ) -from andromede.model.model import PortFieldId + + +@dataclass(frozen=True) +class PortFieldId: + port_name: str + field_name: str @dataclass(eq=True, frozen=True) diff --git a/src/andromede/expression/print.py b/src/andromede/expression/print.py index c01ae76f..b031bee8 100644 --- a/src/andromede/expression/print.py +++ b/src/andromede/expression/print.py @@ -14,11 +14,15 @@ from typing import Dict from andromede.expression.expression import ( + AllTimeSumNode, ComponentParameterNode, ComponentVariableNode, ExpressionNode, PortFieldAggregatorNode, PortFieldNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, ) from andromede.expression.visitor import T @@ -33,8 +37,6 @@ ParameterNode, ScenarioOperatorNode, SubstractionNode, - TimeAggregatorNode, - TimeOperatorNode, VariableNode, ) from .visitor import ExpressionVisitor, visit @@ -98,12 +100,17 @@ def comp_variable(self, node: ComponentVariableNode) -> str: def comp_parameter(self, node: ComponentParameterNode) -> str: return f"{node.component_id}.{node.name}" - # TODO: Add pretty print for node.instances_index - def time_operator(self, node: TimeOperatorNode) -> str: - return f"({visit(node.operand, self)}.{str(node.name)}({node.instances_index}))" + def time_shift(self, node: TimeShiftNode) -> str: + return f"({visit(node.operand, self)}.shift({visit(node.time_shift, self)}))" - def time_aggregator(self, node: TimeAggregatorNode) -> str: - return f"({visit(node.operand, self)}.{str(node.name)}({node.stay_roll}))" + def time_eval(self, node: TimeEvalNode) -> str: + return f"({visit(node.operand, self)}.eval({visit(node.eval_time, self)}))" + + def time_sum(self, node: TimeSumNode) -> str: + return f"({visit(node.operand, self)}.time_sum({visit(node.from_time, self)}, {visit(node.to_time, self)}))" + + def all_time_sum(self, node: AllTimeSumNode) -> str: + return f"({visit(node.operand, self)}.time_sum())" def scenario_operator(self, node: ScenarioOperatorNode) -> str: return f"({visit(node.operand, self)}.{str(node.name)})" diff --git a/src/andromede/expression/time_operator.py b/src/andromede/expression/time_operator.py deleted file mode 100644 index 63059528..00000000 --- a/src/andromede/expression/time_operator.py +++ /dev/null @@ -1,110 +0,0 @@ -# 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. - -""" -Operators that allow temporal manipulation of expressions -""" - -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Any, List, Tuple - - -@dataclass(frozen=True) -class TimeOperator(ABC): - """ - A time operator on an expression is charactirized by two attributes: - - time_ids: int, List[int] or range, is the list of time indices to which the operator applies - - is_rolling: bool, if true, this means that the time_ids are to be understood relatively to the current timestep of the context AND that the represented expression will have to be instanciated for all timesteps. Otherwise, the time_ids are "absolute" times and the expression only has to be instantiated once. - """ - - time_ids: List[int] - - @classmethod - @abstractmethod - def rolling(cls) -> bool: - raise NotImplementedError - - def __post_init__(self) -> None: - if isinstance(self.time_ids, int): - object.__setattr__(self, "time_ids", [self.time_ids]) - elif isinstance(self.time_ids, range): - object.__setattr__(self, "time_ids", list(self.time_ids)) - - def key(self) -> Tuple[int, ...]: - return tuple(self.time_ids) - - def size(self) -> int: - return len(self.time_ids) - - -@dataclass(frozen=True) -class TimeShift(TimeOperator): - """ - Time shift of variables - - Examples: - >>> x.shift([1, 2, 4]) represents the vector of variables (x[t+1], x[t+2], x[t+4]) - """ - - def __str__(self) -> str: - return f"shift({self.time_ids})" - - def __eq__(self, other: Any) -> bool: - return isinstance(other, TimeShift) and self.time_ids == other.time_ids - - def __hash__(self) -> int: - return hash(self.key()) - - @classmethod - def rolling(cls) -> bool: - return True - - -@dataclass(frozen=True) -class TimeEvaluation(TimeOperator): - """ - Absolute time evalaution of variables - - Examples: - >>> x.eval([1, 2, 4]) represents the vector of variables (x[1], x[2], x[4]) - """ - - def __str__(self) -> str: - return f"eval({self.time_ids})" - - def __eq__(self, other: Any) -> bool: - return isinstance(other, TimeEvaluation) and self.time_ids == other.time_ids - - def __hash__(self) -> int: - return hash(self.key()) - - @classmethod - def rolling(cls) -> bool: - return False - - -@dataclass(frozen=True) -class TimeAggregator: - stay_roll: bool - - def size(self) -> int: - return 1 - - -@dataclass(frozen=True) -class TimeSum(TimeAggregator): - def __str__(self) -> str: - return f"sum({self.stay_roll})" - - def __eq__(self, other: Any) -> bool: - return isinstance(other, TimeSum) and self.stay_roll == other.stay_roll diff --git a/src/andromede/expression/visitor.py b/src/andromede/expression/visitor.py index 25bbfb02..11f37b1f 100644 --- a/src/andromede/expression/visitor.py +++ b/src/andromede/expression/visitor.py @@ -19,6 +19,7 @@ from andromede.expression.expression import ( AdditionNode, + AllTimeSumNode, ComparisonNode, ComponentParameterNode, ComponentVariableNode, @@ -32,8 +33,9 @@ PortFieldNode, ScenarioOperatorNode, SubstractionNode, - TimeAggregatorNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, VariableNode, ) @@ -94,11 +96,19 @@ def comp_variable(self, node: ComponentVariableNode) -> T: ... @abstractmethod - def time_operator(self, node: TimeOperatorNode) -> T: + def time_shift(self, node: TimeShiftNode) -> T: ... @abstractmethod - def time_aggregator(self, node: TimeAggregatorNode) -> T: + def time_eval(self, node: TimeEvalNode) -> T: + ... + + @abstractmethod + def time_sum(self, node: TimeSumNode) -> T: + ... + + @abstractmethod + def all_time_sum(self, node: AllTimeSumNode) -> T: ... @abstractmethod @@ -140,10 +150,14 @@ def visit(root: ExpressionNode, visitor: ExpressionVisitor[T]) -> T: return visitor.substraction(root) elif isinstance(root, ComparisonNode): return visitor.comparison(root) - elif isinstance(root, TimeOperatorNode): - return visitor.time_operator(root) - elif isinstance(root, TimeAggregatorNode): - return visitor.time_aggregator(root) + elif isinstance(root, TimeShiftNode): + return visitor.time_shift(root) + elif isinstance(root, TimeEvalNode): + return visitor.time_eval(root) + elif isinstance(root, TimeSumNode): + return visitor.time_sum(root) + elif isinstance(root, AllTimeSumNode): + return visitor.all_time_sum(root) elif isinstance(root, ScenarioOperatorNode): return visitor.scenario_operator(root) elif isinstance(root, PortFieldNode): diff --git a/src/andromede/libs/standard.py b/src/andromede/libs/standard.py index 9a6df24a..19d1ceec 100644 --- a/src/andromede/libs/standard.py +++ b/src/andromede/libs/standard.py @@ -14,7 +14,7 @@ The standard module contains the definition of standard models. """ from andromede.expression import literal, param, var -from andromede.expression.expression import ExpressionRange, port_field +from andromede.expression.expression import port_field from andromede.expression.indexing_structure import IndexingStructure from andromede.model.constraint import Constraint from andromede.model.model import ModelPort, PortFieldDefinition, PortFieldId, model @@ -60,7 +60,7 @@ param("spillage_cost") * var("spillage") + param("ens_cost") * var("unsupplied_energy") ) - .sum() + .time_sum() .expec(), ) @@ -129,7 +129,7 @@ ), ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) @@ -159,7 +159,7 @@ ), # To test both ways of setting constraints ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) @@ -188,11 +188,11 @@ ), Constraint( name="Total storage", - expression=var("generation").sum() <= param("full_storage"), + expression=var("generation").time_sum() <= param("full_storage"), ), ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) @@ -254,22 +254,17 @@ ), Constraint( "Min up time", - var("nb_start") - .shift(ExpressionRange(-param("d_min_up") + 1, literal(0))) - .sum() + var("nb_start").time_sum(-param("d_min_up") + 1, literal(0)) <= var("nb_on"), ), Constraint( "Min down time", - var("nb_stop") - .shift(ExpressionRange(-param("d_min_down") + 1, literal(0))) - .sum() + var("nb_stop").time_sum(-param("d_min_down") + 1, literal(0)) <= param("nb_units_max").shift(-param("d_min_down")) - var("nb_on"), ), - # It also works by writing ExpressionRange(-param("d_min_down") + 1, 0) as ExpressionRange's __post_init__ wraps integers to literal nodes. However, MyPy does not seem to infer that ExpressionRange's attributes are necessarily of ExpressionNode type and raises an error if the arguments in the constructor are integer (whereas it runs correctly), this why we specify it here with literal(0) instead of 0. ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) @@ -331,21 +326,17 @@ ), Constraint( "Min up time", - var("nb_start") - .shift(ExpressionRange(-param("d_min_up") + 1, literal(0))) - .sum() + var("nb_start").time_sum(-param("d_min_up") + 1, literal(0)) <= var("nb_on"), ), Constraint( "Min down time", - var("nb_stop") - .shift(ExpressionRange(-param("d_min_down") + 1, literal(0))) - .sum() + var("nb_stop").time_sum(-param("d_min_down") + 1, literal(0)) <= param("nb_units_max").shift(-param("d_min_down")) - var("nb_on"), ), ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) @@ -360,7 +351,9 @@ definition=-var("spillage"), ) ], - objective_operational_contribution=(param("cost") * var("spillage")).sum().expec(), + objective_operational_contribution=(param("cost") * var("spillage")) + .time_sum() + .expec(), ) UNSUPPLIED_ENERGY_MODEL = model( @@ -375,7 +368,7 @@ ) ], objective_operational_contribution=(param("cost") * var("unsupplied_energy")) - .sum() + .time_sum() .expec(), ) diff --git a/src/andromede/libs/standard_sc.py b/src/andromede/libs/standard_sc.py index 25200638..1031a461 100644 --- a/src/andromede/libs/standard_sc.py +++ b/src/andromede/libs/standard_sc.py @@ -170,7 +170,7 @@ definition=var("p") * param("emission_rate"), ), ], - objective_operational_contribution=(param("cost") * var("p")).sum().expec(), + objective_operational_contribution=(param("cost") * var("p")).time_sum().expec(), ) """ @@ -286,6 +286,6 @@ + param("Pgrad+s_penality") * var("Pgrad+s") + param("Pgrad-s_penality") * var("Pgrad-s") ) - .sum() + .time_sum() .expec(), ) diff --git a/src/andromede/model/model.py b/src/andromede/model/model.py index 3997f43d..d111bff5 100644 --- a/src/andromede/model/model.py +++ b/src/andromede/model/model.py @@ -34,18 +34,21 @@ ) from andromede.expression.degree import is_linear from andromede.expression.expression import ( + AllTimeSumNode, BinaryOperatorNode, ComponentParameterNode, ComponentVariableNode, PortFieldAggregatorNode, PortFieldNode, ScenarioOperatorNode, - TimeAggregatorNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, ) from andromede.expression.indexing import IndexingStructureProvider, compute_indexation from andromede.expression.indexing_structure import IndexingStructure -from andromede.expression.visitor import T, visit +from andromede.expression.port_resolver import PortFieldId +from andromede.expression.visitor import visit from andromede.model.constraint import Constraint from andromede.model.parameter import Parameter from andromede.model.port import PortType @@ -108,12 +111,6 @@ class ModelPort: port_name: str -@dataclass(frozen=True) -class PortFieldId: - port_name: str - field_name: str - - @dataclass(frozen=True) class PortFieldDefinition: """ @@ -274,10 +271,16 @@ def comp_variable(self, node: ComponentVariableNode) -> None: "Port definition must not contain a variable associated to a component." ) - def time_operator(self, node: TimeOperatorNode) -> None: + def time_shift(self, node: TimeShiftNode) -> None: + visit(node.operand, self) + + def time_eval(self, node: TimeEvalNode) -> None: + visit(node.operand, self) + + def time_sum(self, node: TimeSumNode) -> None: visit(node.operand, self) - def time_aggregator(self, node: TimeAggregatorNode) -> None: + def all_time_sum(self, node: AllTimeSumNode) -> None: visit(node.operand, self) def scenario_operator(self, node: ScenarioOperatorNode) -> None: diff --git a/src/andromede/simulation/linear_expression.py b/src/andromede/simulation/linear_expression.py index 344a0988..134269d4 100644 --- a/src/andromede/simulation/linear_expression.py +++ b/src/andromede/simulation/linear_expression.py @@ -19,8 +19,6 @@ from andromede.expression.indexing_structure import IndexingStructure from andromede.expression.scenario_operator import ScenarioOperator -from andromede.expression.time_operator import TimeAggregator, TimeOperator -from andromede.model.model import PortFieldId T = TypeVar("T") @@ -43,6 +41,75 @@ def is_minus_one(value: float) -> bool: return is_close_abs(value, -1, EPS) +@dataclass(frozen=True) +class TimeExpansion: + """ + Carries knowledge of which timesteps this term refers to. + Simplest one is "only the current timestep" + """ + + def get_timesteps(self, current_timestep: int, block_length: int) -> List[int]: + return [current_timestep] + + def apply(self, other: "TimeExpansion") -> "TimeExpansion": + """ + Apply another time expansion on this one. + For example, a shift of -1 applied to a shift one +1 could provide + a no-op TimeExpansion. Not yet supported for now, though. + """ + return other + + +@dataclass(frozen=True) +class AllTimeExpansion(TimeExpansion): + def get_timesteps(self, current_timestep: int, block_length: int) -> List[int]: + return [t for t in range(block_length)] + + def apply(self, other: "TimeExpansion") -> "TimeExpansion": + raise ValueError("No time operation allowed on all-time sum.") + + +@dataclass(frozen=True) +class TimeEvalExpansion(TimeExpansion): + timestep: int + + def get_timesteps(self, current_timestep: int, block_length: int) -> List[int]: + return [self.timestep] + + def apply(self, other: "TimeExpansion") -> "TimeExpansion": + raise ValueError( + "Time operation on evaluated expression not supported for now." + ) + + +@dataclass(frozen=True) +class TimeShiftExpansion(TimeExpansion): + shift: int + + def get_timesteps(self, current_timestep: int, block_length: int) -> List[int]: + return [current_timestep + self.shift] + + def apply(self, other: "TimeExpansion") -> "TimeExpansion": + raise ValueError("Time operation on shifted expression not supported for now.") + + +@dataclass(frozen=True) +class TimeSumExpansion(TimeExpansion): + from_shift: int + to_shift: int + + def get_timesteps(self, current_timestep: int, block_length: int) -> List[int]: + return [ + t + for t in range( + current_timestep + self.from_shift, current_timestep + self.to_shift + ) + ] + + def apply(self, other: "TimeExpansion") -> "TimeExpansion": + raise ValueError("Time operation on time-sums not supported for now.") + + @dataclass(frozen=True) class TermKey: @@ -52,11 +119,30 @@ class TermKey: component_id: str variable_name: str - time_operator: Optional[TimeOperator] - time_aggregator: Optional[TimeAggregator] + time_expansion: TimeExpansion scenario_operator: Optional[ScenarioOperator] +def _str_for_coeff(coeff: float) -> str: + if is_one(coeff): + return "+" + elif is_minus_one(coeff): + return "-" + else: + return "{:+g}".format(coeff) + + +def _str_for_time_expansion(exp: TimeExpansion) -> str: + if isinstance(exp, TimeShiftExpansion): + return f".shift({exp.shift})" + elif isinstance(exp, TimeSumExpansion): + return f".sum({exp.from_shift}, {exp.to_shift})" + elif isinstance(exp, AllTimeExpansion): + return ".sum()" + else: + return "" + + @dataclass(frozen=True) class Term: """ @@ -73,8 +159,7 @@ class Term: structure: IndexingStructure = field( default=IndexingStructure(time=True, scenario=True) ) - time_operator: Optional[TimeOperator] = None - time_aggregator: Optional[TimeAggregator] = None + time_expansion: TimeExpansion = TimeExpansion() scenario_operator: Optional[ScenarioOperator] = None # TODO: It may be useful to define __add__, __sub__, etc on terms, which should return a linear expression ? @@ -82,43 +167,20 @@ class Term: def is_zero(self) -> bool: return is_zero(self.coefficient) - def str_for_coeff(self) -> str: - str_for_coeff = "" - if is_one(self.coefficient): - str_for_coeff = "+" - elif is_minus_one(self.coefficient): - str_for_coeff = "-" - else: - str_for_coeff = "{:+g}".format(self.coefficient) - return str_for_coeff - def __str__(self) -> str: # Useful for debugging tests - result = self.str_for_coeff() + str(self.variable_name) - if self.time_operator is not None: - result += f".{str(self.time_operator)}" - if self.time_aggregator is not None: - result += f".{str(self.time_aggregator)}" + result = _str_for_coeff(self.coefficient) + str(self.variable_name) + result += _str_for_time_expansion(self.time_expansion) if self.scenario_operator is not None: result += f".{str(self.scenario_operator)}" return result - def number_of_instances(self) -> int: - if self.time_aggregator is not None: - return self.time_aggregator.size() - else: - if self.time_operator is not None: - return self.time_operator.size() - else: - return 1 - def generate_key(term: Term) -> TermKey: return TermKey( term.component_id, term.variable_name, - term.time_operator, - term.time_aggregator, + term.time_expansion, term.scenario_operator, ) @@ -140,8 +202,7 @@ def _merge_dicts( v.component_id, v.variable_name, v.structure, - v.time_operator, - v.time_aggregator, + v.time_expansion, v.scenario_operator, ), ), @@ -154,8 +215,7 @@ def _merge_dicts( v.component_id, v.variable_name, v.structure, - v.time_operator, - v.time_aggregator, + v.time_expansion, v.scenario_operator, ), v, @@ -167,8 +227,7 @@ def _merge_is_possible(lhs: Term, rhs: Term) -> None: if lhs.component_id != rhs.component_id or lhs.variable_name != rhs.variable_name: raise ValueError("Cannot merge terms for different variables") if ( - lhs.time_operator != rhs.time_operator - or lhs.time_aggregator != rhs.time_aggregator + lhs.time_expansion != rhs.time_expansion or lhs.scenario_operator != rhs.scenario_operator ): raise ValueError("Cannot merge terms with different operators") @@ -183,8 +242,7 @@ def _add_terms(lhs: Term, rhs: Term) -> Term: lhs.component_id, lhs.variable_name, lhs.structure, - lhs.time_operator, - lhs.time_aggregator, + lhs.time_expansion, lhs.scenario_operator, ) @@ -196,8 +254,7 @@ def _substract_terms(lhs: Term, rhs: Term) -> Term: lhs.component_id, lhs.variable_name, lhs.structure, - lhs.time_operator, - lhs.time_aggregator, + lhs.time_expansion, lhs.scenario_operator, ) @@ -341,8 +398,7 @@ def __imul__(self, rhs: "LinearExpression") -> "LinearExpression": term.component_id, term.variable_name, term.structure, - term.time_operator, - term.time_aggregator, + term.time_expansion, term.scenario_operator, ) _copy_expression(left_expr, self) @@ -373,8 +429,7 @@ def __itruediv__(self, rhs: "LinearExpression") -> "LinearExpression": term.component_id, term.variable_name, term.structure, - term.time_operator, - term.time_aggregator, + term.time_expansion, term.scenario_operator, ) return self @@ -392,26 +447,6 @@ def remove_zeros_from_terms(self) -> None: if is_close_abs(term.coefficient, 0, EPS): del self.terms[term_key] - def is_valid(self) -> bool: - nb_instances = None - for term in self.terms.values(): - term_instances = term.number_of_instances() - if nb_instances is None: - nb_instances = term_instances - else: - if term_instances != nb_instances: - raise ValueError( - "The terms of the linear expression {self} do not have the same number of instances" - ) - return True - - def number_of_instances(self) -> int: - if self.is_valid(): - # All terms have the same number of instances, just pick one - return self.terms[next(iter(self.terms))].number_of_instances() - else: - raise ValueError(f"{self} is not a valid linear expression") - def _copy_expression(src: LinearExpression, dst: LinearExpression) -> None: dst.terms = src.terms diff --git a/src/andromede/simulation/linearize.py b/src/andromede/simulation/linearize.py index 9fe6738a..86565904 100644 --- a/src/andromede/simulation/linearize.py +++ b/src/andromede/simulation/linearize.py @@ -15,10 +15,10 @@ from typing import Optional import andromede.expression.scenario_operator -import andromede.expression.time_operator from andromede.expression.evaluate import ValueProvider -from andromede.expression.evaluate_parameters import get_time_ids_from_instances_index +from andromede.expression.evaluate_parameters import evaluate_time_id from andromede.expression.expression import ( + AllTimeSumNode, ComparisonNode, ComponentParameterNode, ComponentVariableNode, @@ -28,13 +28,38 @@ PortFieldAggregatorNode, PortFieldNode, ScenarioOperatorNode, - TimeAggregatorNode, - TimeOperatorNode, + TimeEvalNode, + TimeShiftNode, + TimeSumNode, VariableNode, ) from andromede.expression.indexing import IndexingStructureProvider from andromede.expression.visitor import ExpressionVisitorOperations, T, visit -from andromede.simulation.linear_expression import LinearExpression, Term, generate_key +from andromede.simulation.linear_expression import ( + AllTimeExpansion, + LinearExpression, + Term, + TimeEvalExpansion, + TimeExpansion, + TimeShiftExpansion, + TimeSumExpansion, + generate_key, +) + + +def _apply_time_expansion( + input: LinearExpression, time_expansion: TimeExpansion +) -> LinearExpression: + result_terms = {} + for term in input.terms.values(): + term_with_operator = dataclasses.replace( + term, time_expansion=term.time_expansion.apply(time_expansion) + ) + result_terms[generate_key(term_with_operator)] = term_with_operator + + # TODO: How can we apply a shift on a parameter ? It seems impossible for now as parameters must already be evaluated... + result_expr = LinearExpression(result_terms, input.constant) + return result_expr @dataclass(frozen=True) @@ -80,42 +105,50 @@ def comp_variable(self, node: ComponentVariableNode) -> LinearExpression: def comp_parameter(self, node: ComponentParameterNode) -> LinearExpression: raise ValueError("Parameters must be evaluated before linearization.") - def time_operator(self, node: TimeOperatorNode) -> LinearExpression: - if self.value_provider is None: - raise ValueError( - "A value provider must be specified to linearize a time operator node. This is required in order to evaluate the value of potential parameters used to specified the time ids on which the time operator applies." - ) + def time_eval(self, node: TimeEvalNode) -> LinearExpression: + operand_expr = visit(node.operand, self) + eval_time = evaluate_time_id(node.eval_time, self._value_provider()) + time_expansion = TimeEvalExpansion(eval_time) + return _apply_time_expansion(operand_expr, time_expansion) + def time_shift(self, node: TimeShiftNode) -> LinearExpression: operand_expr = visit(node.operand, self) - time_operator_cls = getattr(andromede.expression.time_operator, node.name) - time_ids = get_time_ids_from_instances_index( - node.instances_index, self.value_provider - ) + time_shift = evaluate_time_id(node.time_shift, self._value_provider()) + time_expansion = TimeShiftExpansion(time_shift) + return _apply_time_expansion(operand_expr, time_expansion) - result_terms = {} - for term in operand_expr.terms.values(): - term_with_operator = dataclasses.replace( - term, time_operator=time_operator_cls(time_ids) + def time_sum(self, node: TimeSumNode) -> LinearExpression: + operand_expr = visit(node.operand, self) + from_shift = evaluate_time_id(node.from_time, self._value_provider()) + to_shift = evaluate_time_id(node.to_time, self._value_provider()) + time_expansion = TimeSumExpansion(from_shift, to_shift) + if operand_expr.constant != 0: + # We could multiply by number of steps, but not very safe, it might depend on block bounds + # will be handled when refactoring for better parametrs handling + raise ValueError( + "Summing an expression containing a constant is not supported for now." ) - result_terms[generate_key(term_with_operator)] = term_with_operator - - # TODO: How can we apply a shift on a parameter ? It seems impossible for now as parameters must already be evaluated... - result_expr = LinearExpression(result_terms, operand_expr.constant) - return result_expr + return _apply_time_expansion(operand_expr, time_expansion) - def time_aggregator(self, node: TimeAggregatorNode) -> LinearExpression: - # TODO: Very similar to time_operator, may be factorized + def all_time_sum(self, node: AllTimeSumNode) -> LinearExpression: operand_expr = visit(node.operand, self) - time_aggregator_cls = getattr(andromede.expression.time_operator, node.name) - result_terms = {} - for term in operand_expr.terms.values(): - term_with_operator = dataclasses.replace( - term, time_aggregator=time_aggregator_cls(node.stay_roll) + time_expansion = AllTimeExpansion() + if operand_expr.constant != 0: + # We could multiply by number of steps if we had them, but we don't + # will be handled when refactoring for better parametrs handling + raise ValueError( + "Summing an expression containing a constant is not supported for now." ) - result_terms[generate_key(term_with_operator)] = term_with_operator + return _apply_time_expansion(operand_expr, time_expansion) - result_expr = LinearExpression(result_terms, operand_expr.constant) - return result_expr + def _value_provider(self) -> ValueProvider: + if self.value_provider is None: + raise ValueError( + "A value provider must be specified to linearize a time operator node." + " This is required in order to evaluate the value of potential parameters" + " used to specified the time ids on which the time operator applies." + ) + return self.value_provider def scenario_operator(self, node: ScenarioOperatorNode) -> LinearExpression: scenario_operator_cls = getattr( diff --git a/src/andromede/simulation/optimization.py b/src/andromede/simulation/optimization.py index 7542c0e0..f72784be 100644 --- a/src/andromede/simulation/optimization.py +++ b/src/andromede/simulation/optimization.py @@ -36,7 +36,6 @@ from andromede.expression.indexing_structure import IndexingStructure 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 ValueType from andromede.model.constraint import Constraint from andromede.model.model import PortFieldId @@ -460,12 +459,6 @@ def _create_constraint( Adds a component-related constraint to the solver. """ constraint_indexing = _compute_indexing_structure(context, constraint) - - # Perf: Perform linearization (tree traversing) without timesteps so that we can get the number of instances for the expression (from the time_ids of operators) - linear_expr = context.linearize_expression(0, 0, constraint.expression) - # Will there be cases where instances > 1 ? If not, maybe just a check that get_number_of_instances == 1 is sufficient ? Anyway, the function should be implemented - instances_per_time_step = linear_expr.number_of_instances() - for block_timestep in context.opt_context.get_time_indices(constraint_indexing): for scenario in context.opt_context.get_scenario_indices(constraint_indexing): linear_expr_at_t = context.linearize_expression( @@ -488,7 +481,6 @@ def _create_constraint( block_timestep, scenario, constraint_data, - instances_per_time_step, ) @@ -521,7 +513,6 @@ def _create_objective( opt_context, 0, scenario, - 0, ) for solver_var in solver_vars: @@ -548,76 +539,21 @@ def _get_solver_vars( context: OptimizationContext, block_timestep: int, scenario: int, - instance: int, ) -> List[lp.Variable]: + timesteps = term.time_expansion.get_timesteps( + block_timestep, context.block_length() + ) solver_vars = [] - if isinstance(term.time_aggregator, TimeSum): - if isinstance(term.time_operator, TimeShift): - for time_id in term.time_operator.time_ids: - solver_vars.append( - context.get_component_variable( - block_timestep + time_id, - scenario, - term.component_id, - term.variable_name, - term.structure, - ) - ) - elif isinstance(term.time_operator, TimeEvaluation): - for time_id in term.time_operator.time_ids: - solver_vars.append( - context.get_component_variable( - time_id, - scenario, - term.component_id, - term.variable_name, - term.structure, - ) - ) - else: # time_operator is None, retrieve variable for each time step of the block. What happens if we do x.sum() with x not being indexed by time ? Is there a check that it is a valid expression ? - for time_id in range(context.block_length()): - solver_vars.append( - context.get_component_variable( - block_timestep + time_id, - scenario, - term.component_id, - term.variable_name, - term.structure, - ) - ) - - else: # time_aggregator is None - if isinstance(term.time_operator, TimeShift): - solver_vars.append( - context.get_component_variable( - block_timestep + term.time_operator.time_ids[instance], - scenario, - term.component_id, - term.variable_name, - term.structure, - ) - ) - elif isinstance(term.time_operator, TimeEvaluation): - solver_vars.append( - context.get_component_variable( - term.time_operator.time_ids[instance], - scenario, - term.component_id, - term.variable_name, - term.structure, - ) - ) - else: # time_operator is None - # TODO: horrible tous ces if/else - solver_vars.append( - context.get_component_variable( - block_timestep, - scenario, - term.component_id, - term.variable_name, - term.structure, - ) + for t in timesteps: + solver_vars.append( + context.get_component_variable( + t, + scenario, + term.component_id, + term.variable_name, + term.structure, ) + ) return solver_vars @@ -627,41 +563,36 @@ def make_constraint( block_timestep: int, scenario: int, data: ConstraintData, - instances: int, ) -> Dict[str, lp.Constraint]: """ Adds constraint to the solver. """ solver_constraints = {} constraint_name = f"{data.name}_t{block_timestep}_s{scenario}" - for instance in range(instances): - if instances > 1: - constraint_name += f"_{instance}" - solver_constraint: lp.Constraint = solver.Constraint(constraint_name) - constant: float = 0 - for term in data.expression.terms.values(): - solver_vars = _get_solver_vars( - term, - context, - block_timestep, - scenario, - instance, + solver_constraint: lp.Constraint = solver.Constraint(constraint_name) + constant: float = 0 + for term in data.expression.terms.values(): + solver_vars = _get_solver_vars( + term, + context, + block_timestep, + scenario, + ) + for solver_var in solver_vars: + coefficient = term.coefficient + solver_constraint.GetCoefficient( + solver_var ) - for solver_var in solver_vars: - coefficient = term.coefficient + solver_constraint.GetCoefficient( - solver_var - ) - solver_constraint.SetCoefficient(solver_var, coefficient) - # TODO: On pourrait aussi faire que l'objet Constraint n'ait pas de terme constant dans son expression et que les constantes soit déjà prises en compte dans les bornes, ça simplifierait le traitement ici - constant += data.expression.constant + solver_constraint.SetCoefficient(solver_var, coefficient) + # TODO: On pourrait aussi faire que l'objet Constraint n'ait pas de terme constant dans son expression et que les constantes soit déjà prises en compte dans les bornes, ça simplifierait le traitement ici + constant += data.expression.constant - solver_constraint.SetBounds( - data.lower_bound - constant, data.upper_bound - constant - ) + solver_constraint.SetBounds( + data.lower_bound - constant, data.upper_bound - constant + ) - # TODO: this dictionary does not make sense, we override the content when there are multiple instances - solver_constraints[constraint_name] = solver_constraint + # TODO: this dictionary does not make sense, we override the content when there are multiple instances + solver_constraints[constraint_name] = solver_constraint return solver_constraints diff --git a/tests/functional/libs/lib.yml b/tests/functional/libs/lib.yml index 27a8b47e..77e167ca 100644 --- a/tests/functional/libs/lib.yml +++ b/tests/functional/libs/lib.yml @@ -182,7 +182,7 @@ library: - name: on units variation expression: nb_units_on = nb_units_on[t-1] + nb_starting - nb_stoping - name: starting time - expression: sum(nb_starting[t-d_min_up + 1 .. t]) <= nb_units_on + expression: sum(t-d_min_up + 1 .. t, nb_starting) <= nb_units_on - name: stoping time - expression: sum(nb_stoping[t-d_min_down + 1 .. t]) <= nb_units_max - nb_units_on + expression: sum(t-d_min_down + 1 .. t, nb_stoping) <= nb_units_max - nb_units_on objective: expec(sum(cost * production)) \ No newline at end of file diff --git a/tests/functional/test_andromede.py b/tests/functional/test_andromede.py index 8e837a45..ec21cef1 100644 --- a/tests/functional/test_andromede.py +++ b/tests/functional/test_andromede.py @@ -19,22 +19,14 @@ BALANCE_PORT_TYPE, DEMAND_MODEL, GENERATOR_MODEL, - GENERATOR_MODEL_WITH_PMIN, - LINK_MODEL, NODE_BALANCE_MODEL, SHORT_TERM_STORAGE_SIMPLE, SPILLAGE_MODEL, - THERMAL_CLUSTER_MODEL_HD, UNSUPPLIED_ENERGY_MODEL, ) from andromede.model import Model, ModelPort, float_parameter, float_variable, model from andromede.model.model import PortFieldDefinition, PortFieldId -from andromede.simulation import ( - BlockBorderManagement, - OutputValues, - TimeBlock, - build_problem, -) +from andromede.simulation import BlockBorderManagement, TimeBlock, build_problem from andromede.study import ( ConstantData, DataBase, @@ -47,6 +39,43 @@ ) +def test_basic_balance() -> None: + """ + Balance on one node with one fixed demand and one generation, on 1 timestep. + """ + + database = DataBase() + database.add_data("D", "demand", ConstantData(100)) + + database.add_data("G", "p_max", ConstantData(100)) + database.add_data("G", "cost", ConstantData(30)) + + node = Node(model=NODE_BALANCE_MODEL, id="N") + demand = create_component( + model=DEMAND_MODEL, + id="D", + ) + + gen = create_component( + model=GENERATOR_MODEL, + id="G", + ) + + network = Network("test") + network.add_node(node) + network.add_component(demand) + network.add_component(gen) + network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 3000 + + def test_timeseries() -> None: """ Basic case with 2 timesteps, where the demand is 100 on first timestep and 50 on second timestep. @@ -155,7 +184,7 @@ def test_variable_bound() -> None: ) ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) diff --git a/tests/functional/test_performance.py b/tests/functional/test_performance.py index 1c50af1c..4697d6d8 100644 --- a/tests/functional/test_performance.py +++ b/tests/functional/test_performance.py @@ -143,7 +143,7 @@ def test_large_sum_inside_model_with_sum_operator() -> None: structure=IndexingStructure(True, False), ), ], - objective_operational_contribution=(param("cost") * var("var")).sum(), + objective_operational_contribution=(param("cost") * var("var")).time_sum(), ) network = Network("test") diff --git a/tests/functional/test_xpansion.py b/tests/functional/test_xpansion.py index 76ff45eb..4780fef6 100644 --- a/tests/functional/test_xpansion.py +++ b/tests/functional/test_xpansion.py @@ -91,7 +91,7 @@ def thermal_candidate() -> Model: ) ], objective_operational_contribution=(param("op_cost") * var("generation")) - .sum() + .time_sum() .expec(), objective_investment_contribution=param("invest_cost") * var("p_max"), ) @@ -141,7 +141,7 @@ def discrete_candidate() -> Model: ), ], objective_operational_contribution=(param("op_cost") * var("generation")) - .sum() + .time_sum() .expec(), objective_investment_contribution=param("invest_cost") * var("p_max"), ) diff --git a/tests/integration/test_benders_decomposed.py b/tests/integration/test_benders_decomposed.py index 1aef0492..72d9393c 100644 --- a/tests/integration/test_benders_decomposed.py +++ b/tests/integration/test_benders_decomposed.py @@ -91,7 +91,7 @@ def thermal_candidate() -> Model: ) ], objective_operational_contribution=(param("op_cost") * var("generation")) - .sum() + .time_sum() .expec(), objective_investment_contribution=param("invest_cost") * var("p_max"), ) @@ -142,7 +142,7 @@ def discrete_candidate() -> Model: ), ], objective_operational_contribution=(param("op_cost") * var("generation")) - .sum() + .time_sum() .expec(), objective_investment_contribution=param("invest_cost") * var("p_max"), ) diff --git a/tests/models/test_electrolyzer.py b/tests/models/test_electrolyzer.py index e1323070..8798cdf1 100644 --- a/tests/models/test_electrolyzer.py +++ b/tests/models/test_electrolyzer.py @@ -64,7 +64,7 @@ ) ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) diff --git a/tests/models/test_short_term_storage_complex.py b/tests/models/test_short_term_storage_complex.py index 0d23c050..0d4b6d9c 100644 --- a/tests/models/test_short_term_storage_complex.py +++ b/tests/models/test_short_term_storage_complex.py @@ -3,14 +3,9 @@ import pandas as pd from andromede.libs.standard import ( - BALANCE_PORT_TYPE, DEMAND_MODEL, - GENERATOR_MODEL, - GENERATOR_MODEL_WITH_PMIN, - LINK_MODEL, NODE_BALANCE_MODEL, SPILLAGE_MODEL, - THERMAL_CLUSTER_MODEL_HD, UNSUPPLIED_ENERGY_MODEL, ) from andromede.libs.standard_sc import SHORT_TERM_STORAGE_COMPLEX @@ -21,7 +16,6 @@ Network, Node, PortRef, - TimeScenarioIndex, TimeScenarioSeriesData, create_component, ) diff --git a/tests/unittests/data/lib.yml b/tests/unittests/data/lib.yml index 6e57a652..e193d644 100644 --- a/tests/unittests/data/lib.yml +++ b/tests/unittests/data/lib.yml @@ -178,7 +178,7 @@ library: - name: Number of units variation expression: nb_on = nb_on[t-1] + nb_start - nb_stop - name: Min up time - expression: sum(nb_start[t-d_min_up + 1 .. t]) <= nb_on + expression: sum(t-d_min_up + 1 .. t, nb_start) <= nb_on - name: Min down time - expression: sum(nb_stop[t-d_min_down + 1 .. t]) <= nb_units_max[t-d_min_down] - nb_on + expression: sum(t-d_min_down + 1 .. t, nb_stop) <= nb_units_max[t-d_min_down] - nb_on objective: expec(sum(cost * generation)) \ No newline at end of file diff --git a/tests/unittests/expressions/parsing/test_expression_parsing.py b/tests/unittests/expressions/parsing/test_expression_parsing.py index 9620f4be..ba540e9e 100644 --- a/tests/unittests/expressions/parsing/test_expression_parsing.py +++ b/tests/unittests/expressions/parsing/test_expression_parsing.py @@ -15,7 +15,7 @@ from andromede.expression import ExpressionNode, literal, param, print_expr, var from andromede.expression.equality import expressions_equal -from andromede.expression.expression import ExpressionRange, port_field +from andromede.expression.expression import port_field from andromede.expression.parsing.parse_expression import ( AntaresParseException, ModelIdentifiers, @@ -41,22 +41,10 @@ "port.f <= 0", port_field("port", "f") <= 0, ), - ({"x"}, {}, "sum(x)", var("x").sum()), + ({"x"}, {}, "sum(x)", var("x").time_sum()), ({"x"}, {}, "x[-1]", var("x").eval(-literal(1))), - ( - {"x"}, - {}, - "x[-1..5]", - var("x").eval(ExpressionRange(-literal(1), literal(5))), - ), ({"x"}, {}, "x[1]", var("x").eval(1)), ({"x"}, {}, "x[t-1]", var("x").shift(-literal(1))), - ( - {"x"}, - {}, - "x[t-1, t+4]", - var("x").shift([-literal(1), literal(4)]), - ), ( {"x"}, {}, @@ -90,35 +78,23 @@ ( {"x"}, {}, - "x[t-1, t, t+4]", - var("x").shift([-literal(1), literal(0), literal(4)]), + "sum(t-1..t+5, x)", + var("x").time_sum(-literal(1), literal(5)), ), ( {"x"}, {}, - "x[t-1..t+5]", - var("x").shift(ExpressionRange(-literal(1), literal(5))), + "sum(t-1..t, x)", + var("x").time_sum(-literal(1), literal(0)), ), ( {"x"}, {}, - "x[t-1..t]", - var("x").shift(ExpressionRange(-literal(1), literal(0))), - ), - ( - {"x"}, - {}, - "x[t..t+5]", - var("x").shift(ExpressionRange(literal(0), literal(5))), + "sum(t..t+5, x)", + var("x").time_sum(literal(0), literal(5)), ), ({"x"}, {}, "x[t]", var("x")), ({"x"}, {"p"}, "x[t+p]", var("x").shift(param("p"))), - ( - {"x"}, - {}, - "sum(x[-1..5])", - var("x").eval(ExpressionRange(-literal(1), literal(5))).sum(), - ), ({}, {}, "sum_connections(port.f)", port_field("port", "f").sum_connections()), ( {"level", "injection", "withdrawal"}, @@ -133,17 +109,15 @@ ( {"nb_start", "nb_on"}, {"d_min_up"}, - "sum(nb_start[-d_min_up + 1 .. 0]) <= nb_on", - var("nb_start") - .eval(ExpressionRange(-param("d_min_up") + 1, literal(0))) - .sum() + "sum(t - d_min_up + 1 .. t, nb_start) <= nb_on", + var("nb_start").time_sum(-param("d_min_up") + 1, literal(0)) <= var("nb_on"), ), ( {"generation"}, {"cost"}, "expec(sum(cost * generation))", - (param("cost") * var("generation")).sum().expec(), + (param("cost") * var("generation")).time_sum().expec(), ), ], ) diff --git a/tests/unittests/expressions/test_equality.py b/tests/unittests/expressions/test_equality.py index 5d5fd5c2..f949fe7f 100644 --- a/tests/unittests/expressions/test_equality.py +++ b/tests/unittests/expressions/test_equality.py @@ -10,21 +10,10 @@ # # This file is part of the Antares project. -import math - import pytest from andromede.expression import ExpressionNode, copy_expression, literal, param, var from andromede.expression.equality import expressions_equal -from andromede.expression.expression import ( - ExpressionRange, - TimeAggregatorNode, - expression_range, -) - - -def shifted_x() -> ExpressionNode: - return var("x").shift(expression_range(0, 2)) @pytest.mark.parametrize( @@ -36,10 +25,9 @@ def shifted_x() -> ExpressionNode: var("x") - 1, var("x") / 2, var("x") * 3, - var("x").shift(expression_range(1, 10, 2)).sum(), - var("x").shift(expression_range(1, param("p"))).sum(), - TimeAggregatorNode(shifted_x(), name="TimeSum", stay_roll=True), - TimeAggregatorNode(shifted_x(), name="TimeAggregator", stay_roll=True), + var("x").time_sum(1, 10), + var("x").time_sum(1, param("p")), + var("x").time_sum(), var("x") + 5 <= 2, var("x").expec(), ], @@ -56,20 +44,12 @@ def test_equals(expr: ExpressionNode) -> None: (literal(1), literal(2)), (var("x") + 1, var("x")), ( - var("x").shift(expression_range(1, param("p"))).sum(), - var("x").shift(expression_range(1, param("q"))).sum(), - ), - ( - var("x").shift(expression_range(1, 10, 2)).sum(), - var("x").shift(expression_range(1, 10, 3)).sum(), - ), - ( - TimeAggregatorNode(shifted_x(), name="TimeSum", stay_roll=True), - TimeAggregatorNode(shifted_x(), name="TimeSum", stay_roll=False), + var("x").time_sum(1, param("p")), + var("x").time_sum(1, param("q")), ), ( - TimeAggregatorNode(shifted_x(), name="TimeSum", stay_roll=True), - TimeAggregatorNode(shifted_x(), name="TimeAggregator", stay_roll=True), + var("x").time_sum(2, 10), + var("x").time_sum(1, 10), ), (var("x").expec(), var("y").expec()), ], diff --git a/tests/unittests/expressions/test_expressions.py b/tests/unittests/expressions/test_expressions.py index 81bebcd3..d1fdaed2 100644 --- a/tests/unittests/expressions/test_expressions.py +++ b/tests/unittests/expressions/test_expressions.py @@ -39,17 +39,9 @@ from andromede.expression.expression import ( ComponentParameterNode, ComponentVariableNode, - ExpressionRange, - Instances, - comp_param, - comp_var, - port_field, ) from andromede.expression.indexing import IndexingStructureProvider, compute_indexation from andromede.expression.indexing_structure import IndexingStructure -from andromede.model.model import PortFieldId -from andromede.simulation.linear_expression import LinearExpression, Term -from andromede.simulation.linearize import linearize_expression @dataclass(frozen=True) @@ -158,32 +150,6 @@ def get_parameter_value(self, name: str) -> float: assert resolve_parameters(expr, TestParamProvider()) == (5 * x + 3) / 2 -def test_linearization() -> None: - x = comp_var("c", "x") - expr = (5 * x + 3) / 2 - provider = StructureProvider() - - assert linearize_expression(expr, provider) == LinearExpression( - [Term(2.5, "c", "x")], 1.5 - ) - - with pytest.raises(ValueError): - linearize_expression(param("p") * x, provider) - - -def test_linearization_of_non_linear_expressions_should_raise_value_error() -> None: - x = var("x") - expr = x.variance() - - provider = StructureProvider() - with pytest.raises(ValueError) as exc: - linearize_expression(expr, provider) - assert ( - str(exc.value) - == "Cannot linearize expression with a non-linear operator: Variance" - ) - - def test_comparison() -> None: x = var("x") p = param("p") @@ -212,54 +178,26 @@ def get_variable_structure(self, name: str) -> IndexingStructure: def test_shift() -> None: x = var("x") - expr = x.shift(ExpressionRange(literal(1), literal(4))) + expr = x.shift(1) provider = StructureProvider() - assert compute_indexation(expr, provider) == IndexingStructure(True, True) - assert expr.instances == Instances.MULTIPLE -def test_shifting_sum() -> None: +def test_time_sum() -> None: x = var("x") - expr = x.shift(ExpressionRange(literal(1), literal(4))).sum() + expr = x.time_sum(1, 4) provider = StructureProvider() assert compute_indexation(expr, provider) == IndexingStructure(True, True) - assert expr.instances == Instances.SIMPLE - - -def test_eval() -> None: - x = var("x") - expr = x.eval(ExpressionRange(literal(1), literal(4))) - provider = StructureProvider() - - assert compute_indexation(expr, provider) == IndexingStructure(False, True) - assert expr.instances == Instances.MULTIPLE - - -def test_eval_sum() -> None: - x = var("x") - expr = x.eval(ExpressionRange(literal(1), literal(4))).sum() - provider = StructureProvider() - - assert compute_indexation(expr, provider) == IndexingStructure(False, True) - assert expr.instances == Instances.SIMPLE def test_sum_over_whole_block() -> None: x = var("x") - expr = x.sum() + expr = x.time_sum() provider = StructureProvider() assert compute_indexation(expr, provider) == IndexingStructure(False, True) - assert expr.instances == Instances.SIMPLE - - -def test_forbidden_composition_should_raise_value_error() -> None: - x = var("x") - with pytest.raises(ValueError): - _ = x.shift(ExpressionRange(literal(1), literal(4))) + var("y") def test_expectation() -> None: @@ -268,7 +206,6 @@ def test_expectation() -> None: provider = StructureProvider() assert compute_indexation(expr, provider) == IndexingStructure(True, False) - assert expr.instances == Instances.SIMPLE def test_indexing_structure_comparison() -> None: diff --git a/tests/unittests/expressions/test_linear_expressions.py b/tests/unittests/expressions/test_linear_expressions.py index 2564aa8f..f9483394 100644 --- a/tests/unittests/expressions/test_linear_expressions.py +++ b/tests/unittests/expressions/test_linear_expressions.py @@ -14,9 +14,16 @@ import pytest +from andromede.expression.expression import AllTimeSumNode from andromede.expression.scenario_operator import Expectation -from andromede.expression.time_operator import TimeShift, TimeSum -from andromede.simulation.linear_expression import LinearExpression, Term, TermKey +from andromede.simulation.linear_expression import ( + AllTimeExpansion, + LinearExpression, + Term, + TermKey, + TimeShiftExpansion, + TimeSumExpansion, +) @pytest.mark.parametrize( @@ -26,17 +33,19 @@ (Term(-1, "c", "x"), "-x"), (Term(2.50, "c", "x"), "+2.5x"), (Term(-3, "c", "x"), "-3x"), - (Term(-3, "c", "x", time_operator=TimeShift([-1])), "-3x.shift([-1])"), - (Term(-3, "c", "x", time_aggregator=TimeSum(True)), "-3x.sum(True)"), + ( + Term(-3, "c", "x", time_expansion=TimeShiftExpansion(-1)), + "-3x.shift(-1)", + ), + (Term(-3, "c", "x", time_expansion=AllTimeExpansion()), "-3x.sum()"), ( Term( -3, "c", "x", - time_operator=TimeShift([2, 3]), - time_aggregator=TimeSum(False), + time_expansion=TimeSumExpansion(2, 3), ), - "-3x.shift([2, 3]).sum(False)", + "-3x.sum(2, 3)", ), (Term(-3, "c", "x", scenario_operator=Expectation()), "-3x.expec()"), ( @@ -44,10 +53,10 @@ -3, "c", "x", - time_aggregator=TimeSum(True), + time_expansion=AllTimeExpansion(), scenario_operator=Expectation(), ), - "-3x.sum(True).expec()", + "-3x.sum().expec()", ), ], ) @@ -121,25 +130,30 @@ def test_instantiate_linear_expression_from_dict( LinearExpression([Term(5, "c", "y")], 0), LinearExpression([Term(10, "c", "x"), Term(5, "c", "y")], 0), ), - ( - LinearExpression(), - LinearExpression([Term(10, "c", "x", time_operator=TimeShift([-1]))]), - LinearExpression([Term(10, "c", "x", time_operator=TimeShift([-1]))]), - ), ( LinearExpression(), LinearExpression( - [Term(10, "c", "x", time_aggregator=TimeSum(stay_roll=True))] + [Term(10, "c", "x", time_expansion=TimeShiftExpansion(-1))] ), LinearExpression( - [Term(10, "c", "x", time_aggregator=TimeSum(stay_roll=True))] + [Term(10, "c", "x", time_expansion=TimeShiftExpansion(-1))] ), ), + ( + LinearExpression(), + LinearExpression([Term(10, "c", "x", time_expansion=AllTimeExpansion())]), + LinearExpression([Term(10, "c", "x", time_expansion=AllTimeExpansion())]), + ), ( LinearExpression([Term(10, "c", "x")]), - LinearExpression([Term(10, "c", "x", time_operator=TimeShift([-1]))]), LinearExpression( - [Term(10, "c", "x"), Term(10, "c", "x", time_operator=TimeShift([-1]))] + [Term(10, "c", "x", time_expansion=TimeShiftExpansion(-1))] + ), + LinearExpression( + [ + Term(10, "c", "x"), + Term(10, "c", "x", time_expansion=TimeShiftExpansion(-1)), + ] ), ), ( @@ -150,7 +164,7 @@ def test_instantiate_linear_expression_from_dict( 10, "c", "x", - time_operator=TimeShift([-1]), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ] @@ -162,7 +176,7 @@ def test_instantiate_linear_expression_from_dict( 10, "c", "x", - time_operator=TimeShift([-1]), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ), ] @@ -216,7 +230,7 @@ def test_operation_that_leads_to_term_with_zero_coefficient_should_be_removed_fr 10, "c", "x", - time_operator=TimeShift([-1]), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ], @@ -229,7 +243,7 @@ def test_operation_that_leads_to_term_with_zero_coefficient_should_be_removed_fr 20, "c", "x", - time_operator=TimeShift([-1]), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ], @@ -267,8 +281,7 @@ def test_multiplication_of_two_non_constant_terms_should_raise_value_error() -> 10, "c", "x", - time_operator=TimeShift([-1]), - time_aggregator=TimeSum(False), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ], @@ -280,8 +293,7 @@ def test_multiplication_of_two_non_constant_terms_should_raise_value_error() -> -10, "c", "x", - time_operator=TimeShift([-1]), - time_aggregator=TimeSum(False), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ], @@ -312,25 +324,56 @@ def test_negation(e1: LinearExpression, expected: LinearExpression) -> None: LinearExpression([Term(5, "c", "y")], 0), LinearExpression([Term(10, "c", "x"), Term(-5, "c", "y")], 0), ), - ( - LinearExpression(), - LinearExpression([Term(10, "c", "x", time_operator=TimeShift([-1]))]), - LinearExpression([Term(-10, "c", "x", time_operator=TimeShift([-1]))]), - ), ( LinearExpression(), LinearExpression( - [Term(10, "c", "x", time_aggregator=TimeSum(stay_roll=True))] + [ + Term( + 10, + "c", + "x", + time_expansion=TimeShiftExpansion(-1), + ) + ] ), LinearExpression( - [Term(-10, "c", "x", time_aggregator=TimeSum(stay_roll=True))] + [ + Term( + -10, + "c", + "x", + time_expansion=TimeShiftExpansion(-1), + ) + ] ), ), + ( + LinearExpression(), + LinearExpression([Term(10, "c", "x", time_expansion=AllTimeExpansion())]), + LinearExpression([Term(-10, "c", "x", time_expansion=AllTimeExpansion())]), + ), ( LinearExpression([Term(10, "c", "x")]), - LinearExpression([Term(10, "c", "x", time_operator=TimeShift([-1]))]), LinearExpression( - [Term(10, "c", "x"), Term(-10, "c", "x", time_operator=TimeShift([-1]))] + [ + Term( + 10, + "c", + "x", + time_expansion=TimeShiftExpansion(-1), + ) + ] + ), + LinearExpression( + [ + Term(10, "c", "x"), + Term( + -10, + "c", + "x", + time_expansion=TimeShiftExpansion(-1), + ), + ] ), ), ( @@ -341,8 +384,7 @@ def test_negation(e1: LinearExpression, expected: LinearExpression) -> None: 10, "c", "x", - time_operator=TimeShift([-1]), - time_aggregator=TimeSum(False), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ] @@ -354,8 +396,7 @@ def test_negation(e1: LinearExpression, expected: LinearExpression) -> None: -10, "c", "x", - time_operator=TimeShift([-1]), - time_aggregator=TimeSum(False), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ), ] @@ -389,8 +430,7 @@ def test_substraction( 10, "c", "x", - time_operator=TimeShift([-1]), - time_aggregator=TimeSum(False), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ], @@ -403,8 +443,7 @@ def test_substraction( 2, "c", "x", - time_operator=TimeShift([-1]), - time_aggregator=TimeSum(False), + time_expansion=TimeShiftExpansion(-1), scenario_operator=Expectation(), ) ], diff --git a/tests/unittests/expressions/test_linearization.py b/tests/unittests/expressions/test_linearization.py new file mode 100644 index 00000000..746df41d --- /dev/null +++ b/tests/unittests/expressions/test_linearization.py @@ -0,0 +1,89 @@ +import pytest + +from andromede.expression import ExpressionNode, param, var +from andromede.expression.expression import comp_var +from andromede.simulation.linear_expression import ( + AllTimeExpansion, + LinearExpression, + Term, +) +from andromede.simulation.linearize import linearize_expression + +from .test_expressions import ComponentEvaluationContext, StructureProvider + + +def test_linearization() -> None: + x = comp_var("c", "x") + expr = (5 * x + 3) / 2 + provider = StructureProvider() + + assert linearize_expression(expr, provider) == LinearExpression( + [Term(2.5, "c", "x")], 1.5 + ) + + with pytest.raises(ValueError): + linearize_expression(param("p") * x, provider) + + +def test_linearization_of_non_linear_expressions_should_raise_value_error() -> None: + x = var("x") + expr = x.variance() + + provider = StructureProvider() + with pytest.raises(ValueError) as exc: + linearize_expression(expr, provider) + assert ( + str(exc.value) + == "Cannot linearize expression with a non-linear operator: Variance" + ) + + +def test_time_sum_is_distributed_on_expression() -> None: + x = comp_var("c", "x") + y = comp_var("c", "y") + expr = (x + y).time_sum() + provider = StructureProvider() + + assert linearize_expression(expr, provider) == LinearExpression( + [ + Term(1, "c", "x", time_expansion=AllTimeExpansion()), + Term(1, "c", "y", time_expansion=AllTimeExpansion()), + ], + 0, + ) + + +X = comp_var("c", "x") + + +@pytest.mark.parametrize( + "expr", + [(X + 2).time_sum(), (X + 2).time_sum(-1, 2)], +) +def test_sum_of_constant_not_supported( + expr: ExpressionNode, +) -> None: + structure_provider = StructureProvider() + value_provider = ComponentEvaluationContext() + with pytest.raises(ValueError): + linearize_expression(expr, structure_provider, value_provider) + + +@pytest.mark.parametrize( + "expr", + [ + X.shift(-1).shift(+1), + X.shift(-1).time_sum(), + X.shift(-1).time_sum(-2, +2), + X.time_sum().shift(-1), + X.time_sum(-2, +2).shift(-1), + X.eval(2).time_sum(), + ], +) +def test_linearization_of_nested_time_operations_should_raise_value_error( + expr: ExpressionNode, +) -> None: + structure_provider = StructureProvider() + value_provider = ComponentEvaluationContext() + with pytest.raises(ValueError): + linearize_expression(expr, structure_provider, value_provider) diff --git a/tests/unittests/model/test_model_parsing.py b/tests/unittests/model/test_model_parsing.py index 8d42955d..67b086c5 100644 --- a/tests/unittests/model/test_model_parsing.py +++ b/tests/unittests/model/test_model_parsing.py @@ -66,7 +66,7 @@ def test_library_parsing(data_dir: Path) -> None: ) ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) short_term_storage = lib.models["short-term-storage"] diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py index d41628dc..0b135f4a 100644 --- a/tests/unittests/test_data.py +++ b/tests/unittests/test_data.py @@ -89,7 +89,7 @@ def mock_generator_with_fixed_scenario_time_varying_param() -> Model: ) ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) return fixed_scenario_time_varying_param_generator @@ -117,7 +117,7 @@ def mock_generator_with_scenario_varying_fixed_time_param() -> Model: ) ], objective_operational_contribution=(param("cost") * var("generation")) - .sum() + .time_sum() .expec(), ) return scenario_varying_fixed_time_generator diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 3917af54..5916ae8e 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -14,7 +14,6 @@ from andromede.expression.expression import ( ExpressionNode, - ExpressionRange, comp_param, comp_var, literal, @@ -22,8 +21,8 @@ port_field, var, ) -from andromede.model import Constraint, float_parameter, float_variable, model -from andromede.model.model import PortFieldDefinition, port_field_def +from andromede.model import Constraint, float_variable, model +from andromede.model.model import port_field_def @pytest.mark.parametrize( @@ -172,7 +171,7 @@ def test_writing_min_up_constraint_should_represent_all_expected_constraints() - _ = Constraint( "min_up_time", - off_on <= on.shift(ExpressionRange(literal(1), d_min_up)).sum(), + off_on <= on.time_sum(literal(1), d_min_up), ) # Later on, the goal is to assert that when this constraint is sent to the solver, it correctly builds: for all t, for all t' in [t+1, t+d_min_up], off_on[k,t,w] <= on[k,t',w] diff --git a/tests/unittests/test_port.py b/tests/unittests/test_port.py index 2ff4547f..36bfa4a1 100644 --- a/tests/unittests/test_port.py +++ b/tests/unittests/test_port.py @@ -28,7 +28,7 @@ def test_port_type_compatibility_ko() -> None: constraints=[ Constraint( name="Balance", - expression=port_field("balance_port", "flow").sum() == literal(0), + expression=port_field("balance_port", "flow").time_sum() == literal(0), ) ], )