math_spec.model
The YAML surface's types — every block a file may contain, rooted at :class:Spec.
Nothing here has seen data.
CURVATURES = frozenset(get_args(Curvature))
module-attribute
#
Curvature = Literal['convex', 'concave', 'either']
module-attribute
#
DIMENSION_DTYPES = frozenset(get_args(DimensionDtype))
module-attribute
#
DeclaredDtype = ParameterDtype | DimensionDtype
module-attribute
#
DimensionDtype = Literal['float', 'int', 'str', 'datetime']
module-attribute
#
Expression = Annotated[str, BeforeValidator(_number_is_an_expression, json_schema_input_type=str | float)]
module-attribute
#
LinkSign = ComparisonOperator
module-attribute
#
NUMERIC_DTYPES = frozenset({'float', 'int'})
module-attribute
#
ObjectiveSense = Literal['minimize', 'maximize']
module-attribute
#
PARAMETER_DTYPES = frozenset(get_args(ParameterDtype))
module-attribute
#
PIECEWISE_METHODS = {'adjacency': 'a binary per segment, and a row making the two nonzero weights neighbours', 'sos2': 'the same weights, restricted by a set the solver branches on (the sos rules)', 'convex': 'nothing — the weights range over the hull, which is a pure LP', 'lp': 'no weights at all — one row per segment line, plus the two rows holding the domain'}
module-attribute
#
ParameterDtype = Literal['float', 'int', 'bool', 'str']
module-attribute
#
PiecewiseMethod = Literal['adjacency', 'sos2', 'convex', 'lp']
module-attribute
#
SOS_TYPES = frozenset(get_args(SosType))
module-attribute
#
SUPPORTED_VERSIONS = (0,)
module-attribute
#
SosType = Literal[1, 2]
module-attribute
#
VARIABLE_ABSENCE = frozenset(get_args(VariableAbsence))
module-attribute
#
VARIABLE_DOMAINS = frozenset(get_args(VariableDomain))
module-attribute
#
VariableAbsence = Literal['undefined', 'zero']
module-attribute
#
VariableDomain = Literal['continuous', 'integer', 'binary']
module-attribute
#
BoundsBlock
#
ConstraintBlock
#
DimensionBlock
#
Bases: _StrictBlock
A declared dimension, and the dtype its coordinates must be.
A dimension is an axis and nothing else: it declares that the axis exists
and what its coordinates are typed as, never which coordinates there are —
those are data, and arrive at bind time. The maps its members carry — a
generator's bus, a snapshot's period — are top-level lookups:
(:class:LookupBlock), keyed by their own name.
ExpandedPiecewise
#
Bases: _StrictBlock
A piecewise: block after expansion: the block, and the parameters it emitted.
points is the mask the weights carry — the file's own parameter, or
the one derived from a values parameter; starts and ends are the
edge flags an lp block under a mask sits its domain rows on.
ExpressionBlock
#
Bases: _StrictBlock
A named quantity: one arithmetic expression, referenced by the math or read back after a solve.
Written in YAML as a bare string, or as a mapping once it carries a
description: — and serialised back to whichever form it was written in,
so a round trip through :meth:Spec.to_yaml reproduces the file::
expressions:
total_generation: sum(p, over=generator)
emissions:
expression: sum(p * rate, over=generator)
description: CO2 released, the quantity the cap bounds
A quantity whose value varies by region is written as cases: over a
declared foreach:, with an otherwise: for the rest — see the
language reference.
ExpressionCase
#
Bases: _StrictBlock
One region of a named expression: the value, and when it is the value.
Every case says where it applies. The value wherever none of them does is
the block's otherwise:, which is written outside cases: because it
is not a region like these — it is what is left::
cases:
opening: { when: "position(snapshot) == 0", expression: p_max }
otherwise: 0
GivenBlock
#
Bases: _StrictBlock
What the file reads from elsewhere — the interface it is written against.
A file with a given: block says all of its own math and none of the
math it is layered onto. What it does not say is where those declarations
come from: that is the same category of fact as a parameter's values, and
it is bound by whoever builds the model.
GivenConstraintBlock
#
Bases: _StrictBlock
A row family this file reads the dual of and does not introduce.
The frame says how many duals there are and what indexes them; the sense
says what sign one carries. The body is the owner's, so there is no
expression: key: nothing here builds a row, and dual() is the only
thing that may name one of these.
GivenVariableBlock
#
Bases: _StrictBlock
A variable this file reads and does not introduce.
Everything a load-time pass asks of a variable is here — the frame, the
domain, the mask and what absence means on it — and nothing else is. There
is no bounds: key, because whoever owns the column owns its bounds, and
a second spelling of them here would be a second home for one fact.
LookupBlock
#
Bases: _StrictBlock
A named single-valued map out of one dimension into: another.
Its values are labels of into, which is what sum(by=) and
at(by=) land terms on::
lookups:
bus_of: {over: generator, into: bus}
The map itself is data, and arrives at bind time under the lookup's name.
MacroBlock
#
Bases: _StrictBlock
A parameterised expression template, defined in the YAML itself.
Language, not code: formals (args positional, kwargs keyword)
shadow model names inside the template, and every call site expands into
core AST before either backend sees the expression.
ObjectiveBlock
#
ParameterBlock
#
PiecewiseBlock
#
Bases: _StrictBlock
N expressions jointly pinned to a breakpoint-indexed piecewise curve.
Mirrors linopy.Spec.add_piecewise_formulation. Each link is
[expression, values_parameter] or [expression, values_parameter,
sign]: expression is any affine expression string, values_parameter
names a parameter carrying the over dim, and sign bounds the link by
the curve instead of pinning it (at most one non-"==", and only with
exactly two links).
activity = None
class-attribute
instance-attribute
#
curve
property
#
The two links as (x, y), the bounded one last.
Two-link blocks only.
description = None
class-attribute
instance-attribute
#
links
instance-attribute
#
method = 'adjacency'
class-attribute
instance-attribute
#
over
instance-attribute
#
points = None
class-attribute
instance-attribute
#
PiecewiseLink
#
Bases: _StrictBlock
One link of a piecewise block: an expression pinned to a values curve.
Written in YAML as [expression, values] or [expression, values,
sign] and serialised back to exactly that form, so a round trip through
:meth:Spec.to_yaml reproduces the file.
SosBlock
#
Bases: _StrictBlock
A special-ordered set over one dimension of one variable.
One set per coordinate of the variable's foreach minus over; the
members are the variable's existing coordinates along over, in that
dimension's declared order, and big_m is the optional cap a consumer
that reformulates the set puts on its linking rows.
type: 1 admits at most one nonzero member, type: 2 at most two,
and those two consecutive. Unlike every other block this one declares no
math to read off A: it is a set, carried to a consumer that has the
concept and reformulated for one that does not.
Spec
#
Bases: _StrictBlock
The declared math — one YAML file, or one dict, validated. Nothing here has seen data.
A Spec that exists has passed the whole language: constructing one by
any route — to_spec, :meth:model_validate, the constructor — runs
every load-time check, expansion and expression pass included, and raises
:class:~math_spec.errors.LanguageError on a model the language refuses.
Holding one is the proof, so nothing downstream checks it again.
The API is the eleven declaration sections plus version and
description, and two ways back out: :meth:to_dict for the model as
data, :meth:to_yaml for the file a reviewer reads. Everything else on
this class is pydantic's, not a contract this package keeps.
constraints = {}
class-attribute
instance-attribute
#
description = None
class-attribute
instance-attribute
#
dimensions = {}
class-attribute
instance-attribute
#
every_constraint
property
#
Every row family dual() may name, introduced or given.
Not the families a build produces: a given one is already built
elsewhere, so anything counting rows reads constraints instead.
every_variable
property
#
Every variable an expression here may name, introduced or given.
The two block types agree on everything but bounds:, so a reader
that asks a variable for its frame, its mask, its domain or its absence
takes this and needs no branch. One that asks for bounds — lowering,
and the typeset domain line — has to tell them apart, because a given
variable has none to give.
expressions = {}
class-attribute
instance-attribute
#
given = GivenBlock()
class-attribute
instance-attribute
#
lookups = {}
class-attribute
instance-attribute
#
macros = {}
class-attribute
instance-attribute
#
objective = None
class-attribute
instance-attribute
#
parameters = {}
class-attribute
instance-attribute
#
piecewise = {}
class-attribute
instance-attribute
#
sos = {}
class-attribute
instance-attribute
#
variables = {}
class-attribute
instance-attribute
#
version = 0
class-attribute
instance-attribute
#
lookups_of(dimension)
#
The lookups over dimension: name -> the dim they map into.
model_validate(*args, **kwargs)
classmethod
#
Validate a mapping, raising this package's exception tree rather than pydantic's.
__init__ is not wrapped the same way, because defining one makes
pydantic run every after-validator twice.
Source code in src/math_spec/model.py
to_dict()
#
to_yaml()
#
The file a reviewer reads — including for a model that never had one.
VariableBlock
#
Bases: _StrictBlock
A declared decision variable.
absence = 'undefined'
class-attribute
instance-attribute
#
bounds = BoundsBlock()
class-attribute
instance-attribute
#
description = None
class-attribute
instance-attribute
#
domain = 'continuous'
class-attribute
instance-attribute
#
foreach
instance-attribute
#
where = None
class-attribute
instance-attribute
#
undeclared_dimension(kind, name, dimension)
#
The one wording for a declaration naming a dimension the file does not declare.