Spec API#
mathspec.spec contains the file as written: Spec, the class of each entry
in its sections, and the operator names that an expression may call. This page
lists every name that the module exports, in alphabetical order.
The file: what it says, as every entry it may contain, rooted at Spec.
The first public state. A Spec holds one file's sections as the entries
below, and BUILTIN_NAMES is the closed set of operators an expression in
one may call. Nothing here has seen data; what the file means is its
program.
BUILTIN_NAMES = frozenset(BUILTINS)
module-attribute
#
ByName = Annotated[Frozen[str, _T], AfterValidator(_by_name)]
module-attribute
#
A mapping keyed by name, such as a section's entries, sorted by name as it loads.
Curvature = Literal['convex', 'concave', 'either']
module-attribute
#
The shape a curve must have for a method to be exact on it, which the <entry>_curvature assumption states.
convex and concave name the side that a bounded link limits. An entry with both links fixed states either,
which any curve with a single bend meets.
Dims = Annotated[Listed[str], AfterValidator(_sorted)]
module-attribute
#
A dims: list, sorted by name as it loads. Its order reaches nothing a consumer reads, so the spec holds one order
for every file that states the same dimensions.
Expression = Annotated[str, BeforeValidator(_number_is_an_expression, json_schema_input_type=str | float)]
module-attribute
#
An expression as a string, or a number in its place.
FORMULATIONS = ('piecewise', 'sos')
module-attribute
#
Every formulation, in the order Spec.expand expands them. A piecewise: entry may expand into an sos:
entry, and an sos: entry expands into no curve.
Formulation = Literal['piecewise', 'sos']
module-attribute
#
A section of formulations: entries that Spec.expand expands into ordinary entries.
Listed = Annotated[tuple[_T, ...], WrapSerializer(lambda v, write: list(write(v)))]
module-attribute
#
A list in the file. It is a tuple once the spec loads, so nothing appends to it, and a list again in the file the spec writes.
NUMERIC_DTYPES = frozenset({'float', 'int'})
module-attribute
#
The parameter dtypes that may appear where a number belongs, such as a coefficient, a term, a divisor or a bound. A
str parameter is a label and a bool parameter is a condition, so neither is a number.
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)', 'incremental': 'a filled fraction per segment, each filled only once the one before it is full, ordered by a binary', 'convex': 'nothing, so the weights range over the hull, which is a pure LP', 'lp': 'no weights, but one row per segment line, plus the two rows holding the domain'}
module-attribute
#
The methods a piecewise: entry may declare under method:, each with what it adds. sos2, incremental and
lp mean what they mean in linopy.Spec.add_piecewise_formulation (#695).
SOS_TYPES = frozenset(get_args(SosType))
module-attribute
#
The types of special ordered set that a spec may declare.
SUPPORTED_VERSIONS = (0,)
module-attribute
#
The language versions this release of mathspec loads. A language version changes only when the YAML the language accepts
changes, so it is separate from the package version. Version 0 is unstable and has no compatibility promise.
AssumptionSpec
#
Bases: _StrictSpec
What the spec assumes of its data: a condition every coordinate it is checked at has to satisfy.
Written in YAML as a bare string, or as a mapping once it carries a
where: or a description:, and serialised back to whichever form it
was written in::
assumptions:
efficiency_is_a_fraction: "efficiency > 0 AND efficiency <= 1"
bounds_do_not_cross:
holds: "p_min <= p_max"
where: "p_min"
description: a unit with no minimum is unconstrained below
The language decides nothing about the numbers, so the consumer attaching the data checks it, and refuses the data where it does not hold.
description = None
class-attribute
instance-attribute
#
Why the rule exists, in the words of the author. An engine quotes it when it refuses the data.
holds
instance-attribute
#
The condition the data must meet, in the condition grammar, at every coordinate of its dimensions where where is
true.
where = None
class-attribute
instance-attribute
#
The coordinates at which a tool checks the assumption, in the condition grammar. Without it, a tool checks every coordinate.
BoundsSpec
#
Bases: _StrictSpec
Variable bounds — each side is a finite number, a parameter name, or None where it is open.
An omitted bound leaves the variable unbounded on that side, not
implicitly non-negative. An infinity is refused: an open side is null,
and the other infinity leaves no value at all.
ConditionSpec
#
Bases: _StrictSpec
A named condition: one condition, read wherever a where:, a when: or a holds: names it.
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::
conditions:
committable: "Generator_committable AND Generator_active"
stands:
condition: build_year <= period_year AND period_year < build_year + lifetime
description: the generator stands in this period
Its bare name in another condition stands for it, inside count,
shift and at too. Its frame is the dims its condition reads, so
it declares none. It is true or false, so it is never a value in an
expression.
ConstraintSpec
#
DimensionSpec
#
Bases: _StrictSpec
A declared dimension, the dtype its coordinates must be, and whether their order means anything.
A dimension is an axis and nothing else: it declares that the axis exists
and what its coordinates are typed as. Which coordinates there are is data,
and arrives when the data is attached; a one_of only names the labels a
coordinate may have. The maps its members carry — a
generator's bus, a snapshot's period — are top-level relations:
(RelationSpec), keyed by their own name.
description = None
class-attribute
instance-attribute
#
dtype = 'str'
class-attribute
instance-attribute
#
one_of = None
class-attribute
instance-attribute
#
The labels a str dimension allows, at least one and each once, sorted by name. At load, mathspec refuses a
condition that compares the dimension, or a relation column over it, with any other label.
ordered = False
class-attribute
instance-attribute
#
Whether the order in which the data gives the labels is part of the spec. An operator may step or count only along an ordered dimension. On any other dimension, the order of the rows has no meaning.
ExpressionCaseSpec
#
Bases: _StrictSpec
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 entry'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
ExpressionSpec
#
Bases: _StrictSpec
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 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
dims: gives the dimensions over which the quantity is indexed. A plain
entry may leave it out, and its expression then decides them. A declared
dims: gives exactly the dimensions over which the expression is
indexed: an expression indexed over a dimension dims: does not give is
refused, and so is a dims: that gives one over which the expression is
not indexed. A quantity whose value varies by region is
written as cases: over a declared dims:, with an otherwise:
for the rest — see the language reference.
adds_to: adds this entry as a term to the sum it names, a
given: expressions: entry of this file: a write, where the given entry
is the read.
merge adds every term to that name by
name, after the body a file defines where one does. The term is read over at most the
frame the given entry states, and does not read the name it adds to.
adds_to = None
class-attribute
instance-attribute
#
The sum this entry adds to as a term: a given: expressions: entry of this file, or None.
cases = Frozen({})
class-attribute
instance-attribute
#
The cases, by the name of the row each prints, sorted by that name. mathspec proves at load that no two when
conditions overlap, so their order has no meaning.
description = None
class-attribute
instance-attribute
#
dims = None
class-attribute
instance-attribute
#
The dimensions of the expression, sorted by name. An entry with cases: requires it, and a plain entry without
it takes the dimensions of its body.
expression = None
class-attribute
instance-attribute
#
otherwise = None
class-attribute
instance-attribute
#
The value wherever the when of no case is true, printed as the last row.
GivenConditionSpec
#
Bases: _GivenEntrySpec
A named condition this file reads and another file defines.
The frame is all this file states, and it names the dims the definer's condition reads. A condition reads the name as data over that frame, true or false at each coordinate, since a condition reads nothing a solve decides: the condition is the definer's.
GivenConstraintSpec
#
Bases: _GivenEntrySpec
A row family this file reads the dual of and another model builds.
The frame says how many duals there are and what indexes them, which is
what dual() needs. There is no expression:, because nothing here
builds a row.
GivenExpressionSpec
#
Bases: _GivenEntrySpec
A named expression this file reads and another file defines.
The frame is all this file states. This file reads the name as a quantity over that frame, affine in the columns, as it reads a given variable: the body is the definer's, and the composed spec holds the body to the rules of every place this file reads it.
A named expression of this file may add to the name with adds_to:.
merge then writes every term the files
add after the body a file defines, or as the whole body where no file
defines it. The file itself reads the name as the whole sum, alone and
composed.
GivenParameterSpec
#
Bases: _GivenEntrySpec
Data this file reads and another file declares.
It says the frame and the dtype of a ParameterSpec, which are what
this file reads: a condition compares against the dtype, and the dim rules read
the frame. What a missing row means is the declaring file's missing:.
GivenSpec
#
Bases: _StrictSpec
What this file reads and does not build, by kind. Closed at the five kinds.
conditions = Frozen({})
class-attribute
instance-attribute
#
The named conditions another file defines (GivenConditionSpec).
constraints = Frozen({})
class-attribute
instance-attribute
#
The constraints another model builds (GivenConstraintSpec).
expressions = Frozen({})
class-attribute
instance-attribute
#
The named expressions another file defines (GivenExpressionSpec).
parameters = Frozen({})
class-attribute
instance-attribute
#
The parameters another file declares (GivenParameterSpec).
variables = Frozen({})
class-attribute
instance-attribute
#
The variables another file declares (GivenVariableSpec).
GivenVariableSpec
#
Bases: _GivenEntrySpec
A column this file reads and another file introduces.
The frame and the domain are all this file states. The file that introduces the column owns its bounds and its mask.
MacroSpec
#
Bases: _StrictSpec
A parameterised expression template, defined in the YAML itself.
Language, not code: formals (args positional, kwargs keyword)
shadow the spec's names inside the template, and every call site expands in
the syntax tree before resolution reads the expression.
args = ()
class-attribute
instance-attribute
#
description = None
class-attribute
instance-attribute
#
kwargs = ()
class-attribute
instance-attribute
#
The keyword arguments, sorted by name as the spec loads. A call passes each one by name, so their order has no meaning.
template
instance-attribute
#
ObjectiveSpec
#
ParameterSpec
#
Bases: _StrictSpec
A declared parameter with dims and dtype, the coordinates it has, and what a missing row of its data means.
description = None
class-attribute
instance-attribute
#
dims
instance-attribute
#
dtype = 'float'
class-attribute
instance-attribute
#
missing = 'refused'
class-attribute
instance-attribute
#
where = None
class-attribute
instance-attribute
#
The condition that limits the coordinates of the parameter, in the condition grammar. Where it is false, the
parameter is absent and a tool drops its rows of data there. missing: applies where it is true.
PiecewiseLinkSpec
#
Bases: _StrictSpec
One link of a piecewise entry: an expression tied to the curve through a values parameter.
Written in YAML as [expression, values] or [expression, values,
sign], and serialised back to exactly that form, so a round trip through
Spec.to_yaml reproduces the file.
A link that names by: relation[column] reads the curve's weights
through the relation, as at reads an array, and is written as a
mapping. Its row is the entry's dims: with the dims the lookup reads
replaced by the key columns it arrives at, so one link emits a row per fine
coordinate and each reads the curve of the coarse coordinate it maps to.
by = None
class-attribute
instance-attribute
#
The relation and column that the link reads the weights of the curve at, as at(..., by=) names them.
expression
instance-attribute
#
sign = '=='
class-attribute
instance-attribute
#
values
instance-attribute
#
walks
property
#
Whether the link reads the curve's weights through a relation, rather than on the entry's own dims.
PiecewiseSpec
#
Bases: _StrictSpec
Expressions tied to one breakpoint-indexed piecewise curve per coordinate of dims:.
Mirrors linopy.Spec.add_piecewise_formulation. links: maps a name
to [expression, values_parameter] or [expression, values_parameter,
sign]: expression is any affine expression string over dims:,
values_parameter names a parameter carrying along and no dim the
link's row lacks, and sign bounds the link by the curve instead of
pinning it. The name is the link's row in the expansion, <entry>_<link>.
dims: alone decides how many curves the entry builds: one set of
weights per coordinate of it. where: says which of those coordinates
have a curve, and how far each runs along along where it reads that
dim too; activity: whether a curve that exists is switched on. A link
that walks a relation builds a row per fine coordinate, each reading the
one curve its coarse coordinate has, so how many expressions a curve ties
is data.
activity = None
class-attribute
instance-attribute
#
The variable that switches each curve off where it is 0, or no switch when the key is left out. A binary variable,
or an integer one under method: incremental, which from 1 up moves the curve by its point at the first breakpoint
once per unit above 1. Written as the name of the variable, or as at(<variable>, by=<relation>[<column>]).
along
instance-attribute
#
The dimension each curve runs along: its breakpoints, in the declared order of that dimension.
curve
property
#
The two links as (x, y), the bounded one last.
Two-link entries only.
description = None
class-attribute
instance-attribute
#
dims
instance-attribute
#
The frame of the curves, sorted by name as it loads: one curve per coordinate of it.
links
instance-attribute
#
Each link by the name its row takes, <entry>_<link>, in the order the file writes them: with two links pinned,
the first is the x-axis.
method = 'adjacency'
class-attribute
instance-attribute
#
Which of PIECEWISE_METHODS restricts the weights.
where = None
class-attribute
instance-attribute
#
Which coordinates have a curve, or every coordinate when the key is left out. Over dims: alone it states which
curves exist; over along too, it states which breakpoints each curve runs through, for curves of unequal length.
RelationSpec
#
Bases: _StrictSpec
A named relation between dimensions: the columns a row is keyed by, and the columns that key determines.
Each side is a dimension, a list of them, or a mapping of column name to
dimension where two columns share one. key: is the claim the language
checks when the data is attached: one row per key tuple, so every values: column is a
function of it. A relation with no values: is bare — every column is
in its key, a row is its own identity, and nothing reads it::
relations:
gen_bus: {key: generator, values: bus}
gen_bt: {key: [generator], values: [bus, technology]}
zone_of: {key: [generator, period], values: zone}
ends: {key: line, values: {bus0: bus, bus1: bus}}
connection: {key: [generator, bus]}
A sum joins the table on the columns over= names and every other key
column, and groups by the columns by=relation[...] names. A lookup
joins on the columns it names. The entry fixes no direction. A
column is named after its own dimension or after none, so a name in a
call reads the same as a column and as a dimension. The map itself is
data, and arrives with the rest of it, under the relation's name, one
column per role.
description = None
class-attribute
instance-attribute
#
dims
property
#
key
instance-attribute
#
key_roles
property
#
The key roles, however key: was written.
missing = 'refused'
class-attribute
instance-attribute
#
Whether every combination of the dimensions of the key needs a row. Under refused, a tool refuses data that
leaves one out. Under absent, that combination has no row.
pairs
property
#
(role, dimension) per column, the key's columns first.
The program calls the same thing columns;
here the table has no field of its own, being what the two sides make.
roles
property
#
value_roles
property
#
The roles the key determines; empty for a bare relation.
values = None
class-attribute
instance-attribute
#
SosSpec
#
Bases: _StrictSpec
A special-ordered set over one dimension of one variable.
One set per coordinate of the variable's dims minus along; the
members are the variable's existing coordinates along along, in that
dimension's declared order.
type: 1 admits at most one nonzero member, type: 2 at most two,
and those two consecutive. A consumer with the concept takes the set as
one; Spec.expand states it as binaries instead, and the rows it
writes multiply by the member's own bounds, which is why a member
needs both.
Spec
#
Bases: _StrictSpec
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, model_validate, the constructor — runs
every load-time check, expression pass included, and raises
LanguageError on a spec the language refuses.
Holding one is the proof, so nothing downstream checks it again. Nothing
changes one after it loads either: a section is a read-only mapping, a
list is a tuple, and a field cannot be set, so to_yaml writes the
file program was lowered from. override is
the edit, and loads what it makes as a new spec.
The API is the thirteen sections plus version and
description, three ways back out — to_dict for the spec as
data, to_yaml for the file a reviewer reads, expand for the
spec with its formulations written out as plain rows — and program, the
spec typed, which every reader after load walks. Everything else on this
class is pydantic's, not a contract this package keeps.
assumptions = Frozen({})
class-attribute
instance-attribute
#
conditions = Frozen({})
class-attribute
instance-attribute
#
constraints = Frozen({})
class-attribute
instance-attribute
#
description = None
class-attribute
instance-attribute
#
What the file as a whole is, in plain prose like the description: of an entry. mathspec prints it at the top of
the typeset document.
dimensions = Frozen({})
class-attribute
instance-attribute
#
expressions = Frozen({})
class-attribute
instance-attribute
#
given = GivenSpec()
class-attribute
instance-attribute
#
What this file uses and does not build (GivenSpec). It is empty when the file has no given:.
macros = Frozen({})
class-attribute
instance-attribute
#
objective = None
class-attribute
instance-attribute
#
parameters = Frozen({})
class-attribute
instance-attribute
#
piecewise = Frozen({})
class-attribute
instance-attribute
#
program
cached
property
#
This spec typed, section for section — what every reader after load walks.
Computing it is the expression pass, so a spec the language refuses
raises here; loading forces it, so every ask on a spec in hand is the
one object. It mirrors the spec: a piecewise: entry still in it is
a curve under program.piecewise and an sos: entry a set under
program.sos, and expand is what writes either out as rows,
so a consumer building rows reads spec.expand(...).program and
refuses an entry it does not take.
relations = Frozen({})
class-attribute
instance-attribute
#
sos = Frozen({})
class-attribute
instance-attribute
#
variables = Frozen({})
class-attribute
instance-attribute
#
version = 0
class-attribute
instance-attribute
#
The language version the file is written against. A file without it is version 0, which is unstable, so the YAML it accepts may change in any release. A later mathspec refuses a file whose version it cannot load.
expand(*kinds)
#
This spec with its formulations written out as plain variables and constraints.
A formulation states rows rather than being one — piecewise: states
a curve, sos: states which members of a family may be nonzero.
Expanding one writes those rows under names prefixed with the entry's
own, and drops the entry. The result is a different spec: it declares
more variables and constraints, so it does not compare equal to this
one. It declares the same dimensions and parameters, so the same data
attaches to both. Nothing is cached, so a second call builds the
expansion again.
| PARAMETER | DESCRIPTION |
|---|---|
kinds
|
Which formulations to write out —
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Spec
|
The spec with those sections written out, or this same object where |
Spec
|
it declares none of them, so an expansion asked for the same kinds |
Spec
|
again returns itself. It is a spec like any other: |
Spec
|
writes it, and |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
kinds names something that is not a formulation. |
Source code in src/mathspec/spec.py
model_validate(obj, *, strict=None, extra=None, from_attributes=None, context=None, by_alias=None, by_name=None)
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/mathspec/spec.py
to_dict()
#
to_yaml(*, canonical=False)
#
The file a reviewer reads — including for a spec that never had one.
The fields the file wrote, and no others: a default it writes out is
written back, and one it leaves out is not. version is always
written. Sections and the keys of an entry come in the schema's order.
| PARAMETER | DESCRIPTION |
|---|---|
canonical
|
Write the normal form instead: entries sorted by
name, no field at its default, every expression printed from its parsed tree, one term
of a sum per line. Two files that state the same spec write
the same text, so what a diff shows is a difference in the
spec. The normal form loads to the same spec and not to an
equal
TYPE:
|
Source code in src/mathspec/spec.py
VariableSpec
#
Bases: _StrictSpec
A declared decision variable.
bounds = BoundsSpec()
class-attribute
instance-attribute
#
description = None
class-attribute
instance-attribute
#
dims
instance-attribute
#
domain = 'continuous'
class-attribute
instance-attribute
#
missing = 'absent'
class-attribute
instance-attribute
#
where = None
class-attribute
instance-attribute
#
side_columns(written)
#
(role, dimension) per column of one side of a relation, in written order.
A bare name or a list names each column after the dimension it is over; a mapping names the roles, which is what two columns over one dimension need.
Source code in src/mathspec/spec.py
See also
to_spec, which returns aSpec- reading a spec and its program, for what a tool reads from a
Spec - Program API, for what the file means