mathspec#
Write the specification (spec) of an optimisation model as a YAML file. Check it and print it as math, with no data and no solver.
What it is for#
- Check specs in CI, with no data. A wrong name or dimension fails when the file loads, and the error names the fix. Errors →
- Publish the math you solve. The equations in the paper print from the file the solver reads. Typeset →
- One spec, many tools. Engines, renderers and analysers read the spec through one public API, so no two of them can read the file differently. Program API →
- Write full-size specs. The spec of PyPSA's
n.optimize()model is one file, with stochastic, multi-period and quadratic variants. PyPSA in one file →
A spec is one file#
A file states one specification, or spec. A spec declares four things: the axes it runs over, the data it expects, the decisions the solver makes, and the rules those decisions obey. It holds no data: an engine attaches the data and builds a model. The file below is a complete spec.
description: Least-cost dispatch of a generator fleet against an hourly load.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
generator: { description: generating units }
parameters:
capacity: { dims: [generator], description: installed capacity }
load: { dims: [snapshot], description: demand to be met }
cost: { dims: [generator], description: marginal cost }
variables:
dispatch:
description: output of a generator in a snapshot
dims: [snapshot, generator]
where: "capacity > 0"
bounds: { lower: 0, upper: capacity }
constraints:
power_balance:
dims: [snapshot]
expression: sum(dispatch, over=generator) == load
objective:
sense: minimize
expression: sum(dispatch * cost)
The math it prints#
Printed from the file above, with no data and no solver. How shows the call.
Least-cost dispatch of a generator fleet against an hourly load.
Sets#
| Symbol | Meaning |
|---|---|
| \(\mathcal{S}\) | index \(s\) — snapshot — dispatch periods |
| \(\mathcal{G}\) | index \(g\) — generator — generating units |
Parameters#
| Symbol | Meaning |
|---|---|
| \(\bar p\) | capacity over \(\mathcal{G}\) — installed capacity |
| \(\ell\) | load over \(\mathcal{S}\) — demand to be met |
| \(c\) | cost over \(\mathcal{G}\) — marginal cost |
Variables#
| Symbol | Meaning |
|---|---|
| \(\mathit{dispatch}\) | dispatch over \(\mathcal{S} \times \mathcal{G}\) — output of a generator in a snapshot |
Objective#
Subject to#
power_balance
Variable domains#
dispatch
\noindent Least-cost dispatch of a generator fleet against an hourly load.
\paragraph{Sets}
\begin{description}
\item[{$\mathcal{S}$}] index $s$ --- \texttt{snapshot} --- dispatch periods
\item[{$\mathcal{G}$}] index $g$ --- \texttt{generator} --- generating units
\end{description}
\paragraph{Parameters}
\begin{description}
\item[{$\bar p$}] \texttt{capacity} over $\mathcal{G}$ --- installed capacity
\item[{$\ell$}] \texttt{load} over $\mathcal{S}$ --- demand to be met
\item[{$c$}] \texttt{cost} over $\mathcal{G}$ --- marginal cost
\end{description}
\paragraph{Variables}
\begin{description}
\item[{$\mathit{dispatch}$}] \texttt{dispatch} over $\mathcal{S} \times \mathcal{G}$ --- output of a generator in a snapshot
\end{description}
\paragraph{Objective}
\begin{align*}
&& \min & \sum_{s \in \mathcal{S},\ g \in \mathcal{G}} \mathit{dispatch}_{s,g} \cdot c_{g}
\end{align*}
\paragraph{Subject to}
\begin{align*}
\text{power\_balance} && \sum_{g \in \mathcal{G}} \mathit{dispatch}_{s,g} & = \ell_{s} && \forall\, s \in \mathcal{S}
\end{align*}
\paragraph{Variable domains}
\begin{align*}
\text{dispatch} && 0 \le \mathit{dispatch}_{s,g} & \le \bar p_{g} && \forall\, s \in \mathcal{S},\ g \in \mathcal{G} \,:\, \bar p_{g} > 0
\end{align*}
import mathspec as ms
symbols = {
'notation': 'latex',
'dimensions': {
'snapshot': {'index': 's', 'set': '\\mathcal{S}'},
'generator': {'index': 'g', 'set': '\\mathcal{G}'},
},
'names': {
'cost': 'c',
'load': '\\ell',
'capacity': '\\bar p',
},
}
spec = ms.to_spec('dispatch.yaml')
ms.to_latex(spec, symbols=symbols)
ms.to_typst(spec)
ms.to_markdown(spec)
symbols gives every name its conventional spelling. Pass a dict, a YAML path
or a SymbolTable. It is optional: drop it and the same spec prints from the
names in the file, as \(\mathrm{load}_t\) and \(\mathrm{capacity}_g\).
Or from a shell, where the table is that same YAML on disk. --standalone emits
a document that compiles, rather than a fragment to \input:
python -m mathspec latex dispatch.yaml --symbols dispatch.symbols.yaml
python -m mathspec typst dispatch.yaml --standalone -o dispatch.typ
Typeset the math documents the three functions, their options and symbol tables. Each reads the same file every other page here loads.
Engines and other tools#
mathspec builds nothing and solves nothing itself. specsolve and linopy build a model from a spec and its data, and solve it. Support in both is work in progress. Any other tool can read the same spec through the Program API. The solid boxes are mathspec; the dashed boxes are outside it.
flowchart LR
accTitle: What mathspec does, and what other tools do with a spec
accDescr: A YAML file loads into a Spec and the Program it lowers to. mathspec checks the spec and prints it as math, with no data. Outside mathspec, drawn dashed, an engine such as specsolve or linopy reads the same spec, takes your data and returns your answers, and any other tool, such as a renderer or an analyser, reads the same spec through the Program API.
Y(["your spec<br/>one YAML file"]) --> SPEC["<b>Spec</b> and the <b>Program</b> it lowers to<br/><i>checked before any data exists</i>"]
SPEC --> CHECK["<b>check it</b><br/>python -m mathspec check"]
SPEC --> SHOW["<b>print it as math</b><br/>LaTeX · Typst · Markdown"]
SPEC -.-> OTHER["<b>your own tool</b><br/>a renderer · an analyser · …<br/>reads the Program API"]
SPEC -.-> ENGINE["<b>an engine</b><br/>specsolve · linopy<br/>builds and solves the model"]
DATA[("your data")] -.-> ENGINE
ENGINE -.-> ANS(["your answers"])
classDef outside stroke-dasharray:5 4
class ENGINE,DATA,ANS,OTHER outside
Where to next#
- Your first spec: write the file above one block at a time, check it and print it.
- The language: what a file may contain, and what it means.
- Examples: whole specs, each beside the math it prints.
- Print a spec as math: LaTeX, Typst or Markdown, from the file alone.
- Check a spec without data: on your machine and in CI.
- Reading a spec and its program: for whoever writes an engine or a renderer.
Install it#
See installation.
Alpha, pre-1.0
Breaking changes land without a deprecation cycle. Pin an exact version if you depend on this, and read the changelog before upgrading. Every construct round-trips through the schema, the parsers and all three typeset formats, and the LaTeX is compiled. The accepted YAML is not yet frozen.