math_spec.expression_parser
pyparsing-based expression parser for math expressions.
Parses strings like sum(p * cost, over=generator) == load into an AST
that can be evaluated against a namespace of linopy variables and xarray
parameters.
ArithmeticNode is the arithmetic-only union: every nested expression
position (operands, args, kwargs) accepts it and nothing else, and
ComparisonNode appears only at the top of a parsed expression.
ArithmeticNode = NumberNode | NameNode | NameListNode | VariableNode | ParameterNode | DimensionNode | LookupNode | EdgeNode | KeywordNode | UnaryOperatorNode | BinaryOperatorNode | FunctionCallNode | CasesNode
module-attribute
#
BranchNode = UnaryOperatorNode | BinaryOperatorNode | ComparisonNode | FunctionCallNode | CasesNode
module-attribute
#
ComparisonOperator = Literal['<=', '>=', '==']
module-attribute
#
ExpressionNode = ArithmeticNode | ComparisonNode
module-attribute
#
KwargNode = DimensionNode | LookupNode | EdgeNode
module-attribute
#
LeafNode = NumberNode | VariableNode | ParameterNode | KwargNode | UnresolvedNode
module-attribute
#
REAL = '\\d+\\.\\d*([eE][+-]?\\d+)?|\\d+[eE][+-]?\\d+'
module-attribute
#
UnresolvedNode = NameNode | NameListNode | KeywordNode
module-attribute
#
BinaryOperatorNode(op, left, right)
dataclass
#
CaseArm(label, when, value)
dataclass
#
One region of a :class:CasesNode: where it applies, and the value there.
when is None on the last arm and only there — the block's
otherwise:, which is what makes the quantity total without anything
having to prove it. Every other arm's when is proved apart from every
other arm's.
CasesNode(name, arms)
dataclass
#
A value defined by region — a named expression's cases:, inlined.
Built by :mod:math_spec.expansion where a reference to a cased expression
stood; there is no grammar for it, since a file writes the cases on the
declaration rather than at the use site.
Exactly one arm applies at every coordinate: no two when masks can hold
at once, which :mod:math_spec.exclusivity proves at load, and the last arm
— the block's otherwise: — carries no when and so takes whatever the
rest leave. So the arms may be read in any order; the file's is kept because
it is the order they print in. The frame is not carried here: it is on the
declaration, which every consumer needing it already holds.
ComparisonNode(op, left, right)
dataclass
#
DimensionNode(name)
dataclass
#
A resolved reference to a declared dimension.
Only legal in operator kwarg values (sum(x, over=generator)), never as
a value in arithmetic — a dimension is a coordinate space, not data.
name
instance-attribute
#
EdgeNode(policy)
dataclass
#
A resolved edge policy, legal only as an edge= value.
A number in the same position stays a :class:NumberNode: the value the
vacated positions contribute.
policy
instance-attribute
#
FunctionCallNode(name, args=list(), kwargs=dict())
dataclass
#
KeywordNode(value)
dataclass
#
A quoted closed keyword in a kwarg value — shift(..., edge='wrap').
Unresolved: which keywords the kwarg accepts is the operator's business.
value
instance-attribute
#
LookupNode(names, dimension, into)
dataclass
#
A resolved reference to one or more declared lookups, legal only in a kwarg value.
dimension is the one every lookup is over — what sum consumes and
at produces — and into the targets, one per name in the order
written; sum(x, by=[gen_bus, gen_tech]) is one grouping, not two.
NameListNode(names)
dataclass
#
NameNode(name)
dataclass
#
An unresolved token — a name whose kind is not yet known.
The parser cannot know whether p is a variable, a parameter or a
dimension; only the schema knows. resolution.py rewrites every one of
these into one of the typed nodes below, so a NameNode never reaches a
backend. If you find one there, resolution was skipped.
name
instance-attribute
#
ParameterNode(name)
dataclass
#
A resolved reference to a declared parameter.
name
instance-attribute
#
VariableNode(name)
dataclass
#
A resolved reference to a declared decision variable.
name
instance-attribute
#
case_context(name, label)
#
Where an error inside one arm of a cased expression is reported: the declaration, not the use site.
A cased expression is expanded where its name stood, so the context in hand at that point is the constraint's — and naming it would report a case on a constraint that has none.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The named expression the arm belongs to.
TYPE:
|
label
|
The case's name, or
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The context prefix an error message carries. |
Source code in src/math_spec/expression_parser.py
children(node)
#
The sub-expressions of node — the structural half of any walk.
Every pass that recurses the whole tree and acts only at certain leaves
goes through here, so a node added later reaches all of them. A pass whose
answer differs per node type dispatches itself and keeps its
assert_never; this is for the ones that only need to get everywhere.
An operator's kwargs are children too — a dimension or coordinate is an ordinary node in a kwarg value, which is what lets a macro bind a formal.
Source code in src/math_spec/expression_parser.py
parse_expression(text)
#
Parse a math expression string into an AST.
| RAISES | DESCRIPTION |
|---|---|
SchemaError
|
If text is not an expression of the language. |
Source code in src/math_spec/expression_parser.py
shown(names)
#
Names as a kwarg value is written: bare when one, bracketed when several.