Skip to content

Print a spec as math#

This guide shows how to turn a specification file (spec) into the math of a paper or a report, and keep the printed math current. You need mathspec installed and a spec file that loads. The examples use this least-cost dispatch spec, saved as spec.yaml:

spec.yaml
dimensions:
  snapshot: { dtype: int }
  generator: { dtype: str }
parameters:
  capacity: { dims: [generator] }
  cost: { dims: [generator] }
  load: { dims: [snapshot] }
variables:
  dispatch: { dims: [snapshot, generator], bounds: { lower: 0, upper: capacity } }
constraints:
  power_balance:
    dims: [snapshot]
    expression: sum(dispatch, over=generator) == load
objective:
  sense: minimize
  expression: sum(dispatch * cost)

Print the spec as Markdown:

python -m mathspec markdown spec.yaml
import mathspec as ms

print(ms.to_markdown('spec.yaml'))

The output starts with three tables of symbols, for the sets, the parameters and the variables. The bounds of each variable, the objective and the constraints follow, with one equation each. The command and the Python call print the power balance as:

\[ \sum_{g \in \mathcal{G}} \mathit{dispatch}_{g,t} = \mathrm{load}_{t} \qquad \forall\, t \in \mathcal{T} \]

Without a file that maps names to symbols (a symbol table), mathspec picks the symbols itself. It prints the name load as \(\mathrm{load}\) and gives snapshot the index \(t\).

Keep this Python session open, because later steps use ms.

Give the symbols your field's spelling#

Write a symbol table beside the spec, as spec.symbols.yaml:

spec.symbols.yaml
notation: latex

dimensions:
  snapshot: { index: s, set: "\\mathcal{S}" }
  generator: { index: g, set: "\\mathcal{G}" }

names:
  cost: c
  load: "\\ell"
  capacity: "\\bar p"

Pass the table with --symbols, or symbols= in Python:

python -m mathspec markdown spec.yaml --symbols spec.symbols.yaml
print(ms.to_markdown('spec.yaml', symbols='spec.symbols.yaml'))
\[ \sum_{g \in \mathcal{G}} \mathit{dispatch}_{g,s} = \ell_{s} \qquad \forall\, s \in \mathcal{S} \]

The power balance now has \(\ell\) for load and \(s\) for snapshot.

When a key in the table matches nothing in the spec, such as demand: "\\ell" under names:, mathspec refuses the table. The command stops with a traceback, and to_markdown raises a SchemaError. The last line of the traceback is the message, which lists the names that the spec declares:

mathspec.errors.SchemaError: symbol table: 'demand' under names: is not declared by the spec. Declared: capacity, cost, dispatch, load, power_balance.

To print for a typesetting system (Typst), write a symbol table with notation: typst. Given the LaTeX table above, mathspec refuses to print Typst:

python -m mathspec typst spec.yaml --symbols spec.symbols.yaml
ms.to_typst('spec.yaml', symbols='spec.symbols.yaml')
mathspec.errors.SchemaError: symbol table: written in latex, but this render is typst. Write a typst table.

mathspec suggests the rewrite: a second table with notation: typst.

Set a symbol for a name that only an expansion adds#

When mathspec writes a curve out as plain entries (an expansion), it adds names that the file does not declare. A symbol table may also set a symbol for such a name. Here curve.yaml states a cost curve over the dimension bp:

curve.yaml
dimensions:
  bp: { dtype: int, ordered: true }
parameters:
  power_bp: { dims: [bp] }
  cost_bp: { dims: [bp] }
variables:
  power: { dims: [], bounds: { lower: 0 } }
  cost: { dims: [], bounds: { lower: 0 } }
piecewise:
  cost_curve:
    along: bp
    dims: []
    links:
      power: [power, power_bp]
      cost: [cost, cost_bp]
objective:
  sense: minimize
  expression: cost

When mathspec expands cost_curve (piecewise), it adds one weight per breakpoint of the curve, as the variable cost_curve_lam. Set its symbol in a table, and print the expansion:

curve.symbols.yaml
notation: latex
dimensions:
  bp: { index: k }
names:
  cost_curve_lam: "\\lambda"
python -m mathspec markdown curve.yaml --expand --symbols curve.symbols.yaml
print(ms.to_markdown(ms.to_spec('curve.yaml').expand(), symbols='curve.symbols.yaml'))

The command and the Python call print the weights as \(\lambda_{k}\). Without the expansion, mathspec accepts the same table and prints the curve as one line.

Write a document that compiles#

Write the spec as a whole LaTeX or Typst document, with a preamble that wraps the equations:

python -m mathspec latex spec.yaml --symbols spec.symbols.yaml --standalone -o spec.tex
python -m mathspec typst spec.yaml --standalone -o spec.typ
from pathlib import Path

Path('spec.tex').write_text(ms.to_latex('spec.yaml', symbols='spec.symbols.yaml', standalone=True))
Path('spec.typ').write_text(ms.to_typst('spec.yaml', standalone=True))

The commands and the Python code write spec.tex and spec.typ, each a document that compiles on its own. Compile spec.tex with a LaTeX compiler (tectonic), as tectonic spec.tex, or spec.typ with typst compile spec.typ. Each command writes spec.pdf.

To put the math inside a paper, a report or documentation instead, leave out the preamble. Without --standalone, or with standalone=False, you \input the output into a LaTeX document or #include it into a Typst one. For documentation written in Markdown, write the Markdown output to a file that a page of the documentation includes.

Keep the document current#

Run the command of write a document that compiles again before you build a documentation site.

Where make or another build system writes the document, add a rule so that mathspec prints the math whenever the spec or the table changes. In a Makefile, write the rule:

spec.tex: spec.yaml spec.symbols.yaml
    python -m mathspec latex $< --symbols spec.symbols.yaml --standalone -o $@

make spec.tex now writes the document again whenever either file is newer than spec.tex.

See also