math_spec.where_parser
pyparsing-based parser for where strings — grammar and AST only.
Parses strings like "p_max > 0 AND NOT is_must_run" into an AST. What a
mask means is each backend's business: the eager lane evaluates the AST
against an xr.Dataset (builder.evaluate_where), the relational lane
lowers it to SQL predicates (lowering._lower_where).
Kept dependency-free on purpose — validation.py and lowering.py are
linopy-free by hard rule 3, and they import this module.
NotNode, AndNode and OrNode reference the WhereNode union in
their annotations before it is defined, which works only because from
__future__ import annotations makes annotations strings — removing that
future-import requires reordering the definitions.
PredicateOperator = Literal['<=', '>=', '==', '!=', '<', '>']
module-attribute
#
WhereNode = BooleanLiteralNode | UnresolvedNameNode | UnresolvedComparisonNode | UnresolvedPositionNode | DimensionPositionNode | ParameterDefinedNode | VariableDefinedNode | ParameterComparisonNode | DimensionComparisonNode | LookupComparisonNode | LookupPairComparisonNode | LookupDefinedNode | NotNode | AndNode | OrNode
module-attribute
#
DimensionComparisonNode(name, op, value)
dataclass
#
DimensionPositionNode(name, op, position, by=None)
dataclass
#
Compare where a row sits along a dimension against a position.
where: "position(snapshot) == 0" — the boundary of a recurrence named
by where it sits rather than by the label that happens to be there, so the
clause survives the index being relabelled. Negative counts from the end,
-1 being the last.
Both sides are integers, which is what makes every comparator read one way. Naming the coordinate at a position and comparing coordinates against it left an ordering meaning either that or a comparison of positions, and the two part company on an axis whose coordinates do not arrive sorted (#32).
With by it is the boundary of each group the lookup makes —
position(snapshot, by=period_of) == 0 is every period's first snapshot,
and a row reads its own group's, the broadcast at(by=) already defines.
Resolved rather than lowered to a literal: which label sits at a position is a property of the data, so the position travels and each lane reads it off the coordinate order it already holds.
LookupComparisonNode(name, over, op, value)
dataclass
#
Compare a lookup's values against a literal — period_of == 2030.
over is the dimension the lookup maps out of, copied off the
declaration during resolution so the frame check and both lanes read it
here rather than looking the lookup up again.
LookupDefinedNode(name, over)
dataclass
#
True where the named lookup has a value — the partial-lookup case.
A lookup may be partial: a null says the label belongs to no group (a generator on no bus, a line with one open end). This is how a declaration asks for the labels that do map, spelled as a bare name exactly as a parameter's definedness is.
LookupPairComparisonNode(name, other, over, op)
dataclass
#
Compare two lookups over one dimension — from != to.
The one comparison whose both sides are structure: two maps out of the same dimension, tested row by row on that dimension's own table. Over different dims there is no row to compare them on, which resolution refuses.
ParameterComparisonNode(name, op, value)
dataclass
#
ParameterDefinedNode(name)
dataclass
#
True wherever the named parameter is non-null and finite.
name
instance-attribute
#
UnresolvedComparisonNode(name, op, value, quoted=False)
dataclass
#
UnresolvedNameNode(name)
dataclass
#
A bare name — unresolved. resolution.py types it.
name
instance-attribute
#
UnresolvedPositionNode(dimension, op, position, by=None)
dataclass
#
position(dim) <op> i before the name is checked.
Kept apart from :class:UnresolvedComparisonNode because its left-hand
side is not a name but an application to one, which no bare name can
carry. resolution.py types it into :class:DimensionPositionNode.
VariableDefinedNode(name)
dataclass
#
True at the coordinates where the named variable exists.
The variable counterpart of :class:ParameterDefinedNode, and spelled the
same way — a bare name. A parameter's bare name asks whether it has a value
here; a variable's asks whether it exists here.
name
instance-attribute
#
parse_where(text)
#
Parse a where string into an AST.
With parse_all and a single top-level alternative, element 0 of the
parse result is the root node.