Skip to content

Every construct, as math#

Typesetting prints a model the way a paper prints it. This page prints all of it: every construct the language has, beside the math the typesetter gives it, so the notation can be read as the one system it has to be — two constructs that mean different things looking different, a symbol introduced where it is defined and used where it is meant.

It is generated by pixi run python -m tools.notation, almost all of it from one model: tests/typesetting/golden/model.yaml, which is not a sensible optimisation problem and is not trying to be: it is the one file that carries every construct at once, and three checks in tests/typesetting/test_typeset.py hold it to the language — every operator a format spells, every node kind the parsers produce, every line of the walk. So every here is asserted rather than promised, and a construct added to the language arrives on this page or CI goes red. The curves are the exception, one real model per method:, for the reason the section gives.

Two things this page is not. It is not the operator reference — what each operator does is Operators, which renders the same math one row per call shape. And it is not a tutorial: the models under examples/ are the ones written to be read.

The symbols below are derived from the names in the file, which is what a model prints with no setup, so you see \(\mathrm{load}_{t}\) rather than \(\ell_t\). A symbol table replaces every symbol, and changes nothing else on this page.

The legend#

A dimension, a relation and a parameter declare no equation; what they print is the legend every model opens with.

dimensions:
  snapshot: { dtype: int }
  generator: { dtype: str }
  bus: { dtype: str }
  zone: { dtype: str }
  season: { dtype: str }
  technology: { dtype: str }

relations:
  gen_bus: { key: generator, values: bus }
  zone_of: { key: bus, values: zone }
  area_of: { key: bus, values: zone } # a second map into the same set, to compare against
  season_of: { key: snapshot, values: season }
  gen_zone: { key: [generator, snapshot], values: zone } # a map keyed by two dimensions: a call consumes one and joins on the other
  rep_of: { key: snapshot, values: { rep: snapshot } } # a map into its own dimension: the representative snapshot
  connection: { key: [generator, bus] } # a bare relation, with no value columns: many-to-many, read only by sum with both ends named
  gen_bt: { key: generator, values: [bus, technology] } # one table with two value columns, read to both at once

parameters:
  p_max: { dims: [generator] }
  p_min: { dims: [generator] }
  cost: { dims: [generator] }
  load: { dims: [snapshot, bus] }
  is_flexible: { dims: [generator], dtype: bool }
  zone_cap: { dims: [zone] }
  tech_cap: { dims: [bus, technology] }
  min_up: { dims: [generator], dtype: int }
  eta: { dims: [generator] } # a Greek name that is *given*, so the rule wins and it prints as the word
  lead: { dims: [generator], dtype: int }
  budget: { dims: [] } # scalar: the legend says so rather than printing an empty product
  growth: { dims: [] } # the base of a power; the exponent is `lead`, a column

Sets#

Symbol Meaning
\(\mathcal{T}\) index \(t\) — snapshot (int coordinates) with \(\mathrm{season\_of}: \mathcal{T} \to \mathcal{S},\ \mathrm{gen\_zone}: \mathcal{G} \times \mathcal{T} \to \mathcal{Z},\ \mathrm{rep\_of}: \mathcal{T} \to \mathcal{T}\)
\(\mathcal{G}\) index \(g\) — generator with \(\mathrm{gen\_bus}: \mathcal{G} \to \mathcal{B},\ \mathrm{gen\_zone}: \mathcal{G} \times \mathcal{T} \to \mathcal{Z},\ \mathrm{connection} \subseteq \mathcal{G} \times \mathcal{B},\ \mathrm{gen\_bt}: \mathcal{G} \to \mathcal{B} \times \mathcal{E}\)
\(\mathcal{B}\) index \(b\) — bus with \(\mathrm{gen\_bus}: \mathcal{G} \to \mathcal{B},\ \mathrm{zone\_of}: \mathcal{B} \to \mathcal{Z},\ \mathrm{area\_of}: \mathcal{B} \to \mathcal{Z},\ \mathrm{connection} \subseteq \mathcal{G} \times \mathcal{B},\ \mathrm{gen\_bt}: \mathcal{G} \to \mathcal{B} \times \mathcal{E}\)
\(\mathcal{Z}\) index \(z\) — zone with \(\mathrm{zone\_of}: \mathcal{B} \to \mathcal{Z},\ \mathrm{area\_of}: \mathcal{B} \to \mathcal{Z},\ \mathrm{gen\_zone}: \mathcal{G} \times \mathcal{T} \to \mathcal{Z}\)
\(\mathcal{S}\) index \(s\) — season with \(\mathrm{season\_of}: \mathcal{T} \to \mathcal{S}\)
\(\mathcal{E}\) index \(e\) — technology with \(\mathrm{gen\_bt}: \mathcal{G} \to \mathcal{B} \times \mathcal{E}\)

Parameters#

Symbol Meaning
\(\mathrm{p}^{\mathrm{max}}\) p_max over \(\mathcal{G}\)
\(\mathrm{p}^{\mathrm{min}}\) p_min over \(\mathcal{G}\)
\(\mathrm{cost}\) cost over \(\mathcal{G}\)
\(\mathrm{load}\) load over \(\mathcal{T} \times \mathcal{B}\)
\(\mathrm{is\_flexible}\) is_flexible over \(\mathcal{G}\)
\(\mathrm{zone\_cap}\) zone_cap over \(\mathcal{Z}\)
\(\mathrm{tech\_cap}\) tech_cap over \(\mathcal{B} \times \mathcal{E}\)
\(\mathrm{min\_up}\) min_up over \(\mathcal{G}\)
\(\mathrm{eta}\) eta over \(\mathcal{G}\)
\(\mathrm{lead}\) lead over \(\mathcal{G}\)
\(\mathrm{budget}\) budget (scalar)
\(\mathrm{growth}\) growth (scalar)

Variables#

Symbol Meaning
\(p\) p over \(\mathcal{T} \times \mathcal{G}\)
\(\mathit{spill}\) spill over \(\mathcal{T}\)
\(\mathit{slack}\) slack over \(\mathcal{T}\)
\(\theta\) theta over \(\mathcal{B}\)
\(\mathit{on}\) on over \(\mathcal{T} \times \mathcal{G}\)
\(\mathit{units}\) units over \(\mathcal{G}\)
\(\mathit{spare}\) spare over \(\mathcal{G}\)
\(\mathit{reserve}\) reserve (scalar)
\(\mathit{headroom}\) headroom (scalar)
\(\mathit{weight}\) weight over \(\mathcal{T} \times \mathcal{G}\)

Definitions#

Symbol Meaning
\(\mathit{spend}\) spend over \(\mathcal{T}\) — what a snapshot's dispatch costs
\(\mathit{lcoe}\) lcoe (scalar)
\(\mathit{marginal\_price}\) marginal_price over \(\mathcal{T} \times \mathcal{B}\)
\(\mathrm{startup\_cost}\) startup_cost over \(\mathcal{T} \times \mathcal{G}\) — what starting a unit in this snapshot costs, which the horizon's edge changes

Upright is what the model is given — a parameter such as \(\mathrm{p}^{\mathrm{max}}\), a coordinate map, a label — and italic is what the solver chooses, such as \(p\). An index is italic too, being what a quantifier chooses, and a set is script.

\(t \ominus k\) denotes cyclic translation: index \(t-k\) taken modulo the size of the dimension (roll). Plain \(t-k\) (shift) has no wraparound — terms translated past the edge are simply absent.

\(t \boxminus_{v} k\) denotes translation with \(v\) standing where index \(t-k\) leaves the dimension (shift(edge=v)), so the row at that boundary is built and carries \(v\) rather than being dropped.

\(t \ominus^{\mathrm{relation}(t)} k\) denotes a translation counted inside the group a relation puts \(t\) in (shift(by=relation)), so a term never crosses out of its own group. The two modifiers take different slots — the group above, the fill below — so \(t \boxminus_{v}^{\mathrm{relation}(t)} k\) is both at once.

\(\mathrm{pos}(t)\) denotes where index \(t\) sits along its dimension's own order — the order shift steps along, not the order labels sort in — counted from \(0\). The index itself stays the coordinate, so \(t\) compares against labels and \(\mathrm{pos}(t)\) against positions.

\(\mathrm{pos}_{\mathrm{relation}(t)}(t)\) counts within the group a relation puts \(t\) in: the subscript names the map, \(\mathcal{T}_{\mathrm{relation}(t)}\) is the group it lands in, and that group has a first position of its own.

\(\lvert \mathcal{T} \rvert\) denotes the size of the set being counted along, and a position counted from the end prints against it — \(\lvert \mathcal{T} \rvert - 1\) is the last position, one less than the size because the first is \(0\).

The objective#

objective#

a sense, a product of two variables, a power over two parameters, a power of one of those, and the summations a scalar objective spells out beside two scalar terms

sense: maximize
expression: sum(p * cost) + sum(p * p * cost) + sum(p * cost * growth ** lead) + sum(p * (growth ** lead) ** 2) + sum(p * p_max) - reserve + -headroom
\[ \max \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot p_{t,g} \cdot \mathrm{cost}_{g} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} \cdot \mathrm{growth}^{\mathrm{lead}_{g}} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \left( \mathrm{growth}^{\mathrm{lead}_{g}} \right)^{2} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \mathrm{p}^{\mathrm{max}}_{g} - \mathit{reserve} - \mathit{headroom} \]

Constraints#

budgeted#

names the plain expression: its symbol prints here, its definition once below

budgeted:
  dims: [snapshot]
  expression: spend <= budget
\[ \mathit{spend}_{t} \le \mathrm{budget} \qquad \forall\, t \in \mathcal{T} \]

starts#

names the cased expression: its symbol prints here, its block once below

starts:
  dims: [snapshot, generator]
  expression: p <= startup_cost
\[ p_{t,g} \le \mathrm{startup\_cost}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

balance#

sum over a relation

balance:
  dims: [snapshot, bus]
  expression: sum(p, over=gen_bus.generator) + spill - slack == load
\[ \sum_{g \in \mathcal{G} \,:\, \mathrm{gen\_bus}(g) = b} p_{t,g} + \mathit{spill}_{t} - \mathit{slack}_{t} = \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \]

ramp#

roll (cyclic) and shift (acyclic) in one equation

ramp:
  dims: [snapshot, generator]
  expression: p - shift(p, along=snapshot, offset=1, edge='wrap') <= shift(p, along=snapshot, offset=1) + p_max
\[ p_{t,g} - p_{t \ominus 1,g} \le p_{t - 1,g} + \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

edges#

the two translations ramp leaves out: a fill, and forwards

edges:
  dims: [snapshot, generator]
  expression: >-
    shift(p, along=snapshot, offset=1, edge=0)
    <= shift(p, along=snapshot, offset=-1, edge=0) + p_max
\[ p_{t \boxminus_{0} 1,g} \le p_{t \boxplus_{0} 1,g} + \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

ahead#

the cyclic translation forwards, which is a fourth symbol again

ahead:
  dims: [snapshot, generator]
  expression: p <= shift(p, along=snapshot, offset=-1, edge='wrap')
\[ p_{t,g} \le p_{t \oplus 1,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

composed#

two steps of one policy are one step; a zero step is none at all

composed:
  dims: [snapshot, generator]
  expression: shift(shift(p, along=snapshot, offset=1), along=snapshot, offset=1) <= shift(p_max, along=generator, offset=0)
\[ p_{t - 2,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

uncomposed#

a named offset under a numbered one stays two steps, not their sum

uncomposed:
  dims: [snapshot, generator]
  expression: shift(shift(p, along=snapshot, offset=lead, edge=0), along=snapshot, offset=1) <= p_max
\[ p_{\left( t - 1 \right) \boxminus_{0} \mathrm{lead},g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

crossed#

two dimensions translated at one leaf, each with its own policy

crossed:
  dims: [snapshot, generator]
  expression: shift(shift(p, along=snapshot, offset=1, edge='wrap'), along=generator, offset=-1) <= p_max
\[ p_{t \ominus 1,g + 1} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

lead_time#

an offset the data carries, so it prints as a symbol rather than a number

lead_time:
  dims: [snapshot, generator]
  expression: shift(p, along=snapshot, offset=lead, edge=0) <= p_max
\[ p_{t \boxminus_{0} \mathrm{lead},g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

in_season#

a translation partitioned by a relation: the group rides on the operator

in_season:
  dims: [snapshot, generator]
  expression: p <= shift(p, along=season_of.snapshot, offset=1, edge='wrap', within=season)
\[ p_{t,g} \le p_{t \ominus^{\mathrm{season\_of}(t)} 1,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

held_in_season#

the same group, with a fill: each season's opening row is kept and given a zero

held_in_season:
  dims: [snapshot, generator]
  expression: p <= shift(p, along=season_of.snapshot, offset=1, edge=0, within=season)
\[ p_{t,g} \le p_{t \boxminus_{0}^{\mathrm{season\_of}(t)} 1,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

window#

a trailing window of fixed width

window:
  dims: [snapshot, generator]
  expression: sum_back(on, along=snapshot, window=3) <= units
\[ \sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < 3} \mathit{on}_{t',g} \le \mathit{units}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

history#

the same window, its width in the data and its edge wrapped

history:
  dims: [snapshot, generator]
  expression: sum_back(on, along=snapshot, window=min_up, edge='wrap') <= units
\[ \sum_{t' \in \mathcal{T} \,:\, 0 \le t \ominus t' < \mathrm{min\_up}} \mathit{on}_{t',g} \le \mathit{units}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

seasonal_window#

a window partitioned by a relation: the group rides on the operator

seasonal_window:
  dims: [snapshot, generator]
  expression: sum_back(on, along=season_of.snapshot, window=3, within=season) <= units
\[ \sum_{t' \in \mathcal{T} \,:\, 0 \le t -^{\mathrm{season\_of}(t)} t' < 3} \mathit{on}_{t',g} \le \mathit{units}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

pullback#

at(), which re-indexes through a relation instead of an offset

pullback:
  dims: [snapshot, bus]
  expression: spill <= at(zone_cap, by=zone_of, over=zone, into=bus)
\[ \mathit{spill}_{t} \le \mathrm{zone\_cap}_{\mathrm{zone\_of}(b)} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \]

grouped_once#

one table read to two value columns: the domain carries a condition per column

grouped_once:
  dims: [snapshot, bus, technology]
  expression: sum(p, by=gen_bt.[bus, technology]) <= tech_cap
\[ \sum_{g \in \mathcal{G} \,:\, \mathrm{gen\_bt.bus}(g) = b \wedge \mathrm{gen\_bt.technology}(g) = e} p_{t,g} \le \mathrm{tech\_cap}_{b,e} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B},\ e \in \mathcal{E} \]

pulled_back_once#

its adjoint, reading one slot through two columns of one table

pulled_back_once:
  dims: [generator]
  expression: units <= at(tech_cap, by=gen_bt, over=[bus, technology], into=generator)
\[ \mathit{units}_{g} \le \mathrm{tech\_cap}_{\mathrm{gen\_bt.bus}(g),\mathrm{gen\_bt.technology}(g)} \qquad \forall\, g \in \mathcal{G} \]

within_bus#

a partition grouped by one named value column of a two-value table, and a position within both

within_bus:
  dims: [generator]
  where: "position(gen_bt.generator, within=[bus, technology]) == 0"
  expression: units <= shift(units, along=gen_bt.generator, offset=1, edge=0, within=bus)
\[ \mathit{units}_{g} \le \mathit{units}_{g \boxminus_{0}^{\mathrm{gen\_bt.bus}(g)} 1} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{pos}_{\left( \mathrm{gen\_bt.bus}(g),\ \mathrm{gen\_bt.technology}(g) \right)}(g) = 0 \]

relational#

a sum through a bare relation: the domain is a row of the relation rather than a function's value

relational:
  dims: [snapshot, bus]
  expression: sum(p, by=connection.bus) <= load
\[ \sum_{g \in \mathcal{G} \,:\, \left( g,\ b \right) \in \mathrm{connection}} p_{t,g} \le \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \]

connected#

a bare relation as a where: the row of the frame has to be a member of the relation

connected:
  dims: [snapshot, generator, bus]
  where: "connection"
  expression: p <= load
\[ p_{t,g} \le \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \left( g,\ b \right) \in \mathrm{connection} \]

representative#

a map into its own dimension, read both ways: the frame is unchanged and the index is primed

representative:
  dims: [snapshot]
  expression: sum(spill, over=rep_of.snapshot) <= at(spill, by=rep_of, over=rep, into=snapshot)
\[ \sum_{t' \in \mathcal{T} \,:\, \mathrm{rep\_of}(t') = t} \mathit{spill}_{t'} \le \mathit{spill}_{\mathrm{rep\_of}(t)} \qquad \forall\, t \in \mathcal{T} \]

zonal#

a grouping through a two-key map, consuming one key: the condition reads the other, and the row keeps it

zonal:
  dims: [snapshot, zone]
  expression: sum(p, over=gen_zone.generator) <= zone_cap
\[ \sum_{g \in \mathcal{G} \,:\, \mathrm{gen\_zone}(g,\ t) = z} p_{t,g} \le \mathrm{zone\_cap}_{z} \qquad \forall\, t \in \mathcal{T},\ z \in \mathcal{Z} \]

zonal_history#

the same table consuming its other key

zonal_history:
  dims: [generator, zone]
  expression: sum(p, over=gen_zone.snapshot) <= zone_cap
\[ \sum_{t \in \mathcal{T} \,:\, \mathrm{gen\_zone}(g,\ t) = z} p_{t,g} \le \mathrm{zone\_cap}_{z} \qquad \forall\, g \in \mathcal{G},\ z \in \mathcal{Z} \]

zonal_membership#

the same table read between its two key columns: no value column is read, so the domain asks only that the row is there

zonal_membership:
  dims: [snapshot]
  expression: sum(units, by=gen_zone.snapshot) <= budget
\[ \sum_{g \in \mathcal{G} \,:\, \mathrm{gen\_zone}(g,\ t) \text{ is defined}} \mathit{units}_{g} \le \mathrm{budget} \qquad \forall\, t \in \mathcal{T} \]

zonal_pullback#

its adjoint, reading the slot the row's own snapshot puts the generator in

zonal_pullback:
  dims: [snapshot, generator]
  where: "gen_zone == 'north' AND position(gen_zone.generator, within=zone) == 0"
  expression: p <= at(spill * zone_cap, by=gen_zone, into=generator, over=zone)
\[ p_{t,g} \le \mathit{spill}_{t} \cdot \mathrm{zone\_cap}_{\mathrm{gen\_zone}(g,\ t)} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{gen\_zone}(g,\ t) = \text{'}\mathrm{north}\text{'} \wedge \mathrm{pos}_{\mathrm{gen\_zone}(g,\ t)}(g) = 0 \]

arithmetic#

division, both unary signs, a sign beside a sign, floats with and without an exponent, bracketing

arithmetic:
  dims: [snapshot]
  expression: >-
    sum(p / 2 + -cost - -1e-5 * p + 2.5e-7 * cost + 0.5 * p, over=generator)
    >= -sum(+p, over=generator) * -3
\[ \sum_{g \in \mathcal{G}} \left( \frac{p_{t,g}}{2} - \mathrm{cost}_{g} + 10^{-5} \cdot p_{t,g} + 2.5 \times 10^{-7} \cdot \mathrm{cost}_{g} + 0.5 \cdot p_{t,g} \right) \ge -\left( \sum_{g \in \mathcal{G}} p_{t,g} \right) \cdot \left( -3 \right) \qquad \forall\, t \in \mathcal{T} \]

total#

a sum naming no dim, whose domain is the one place the dims it took are said

total:
  dims: []
  expression: sum(p) <= budget
\[ \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \le \mathrm{budget} \]

scalar#

a parameter over nothing, and a mask that is a bare parameter

scalar:
  dims: [generator]
  where: "cost"
  expression: units <= budget
\[ \mathit{units}_{g} \le \mathrm{budget} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{cost}_{g} \text{ is defined} \]

running#

a mask on a variable's existence, and one on a dimension's label

running:
  dims: [snapshot, bus]
  where: "theta AND snapshot >= 3"
  expression: theta <= load
\[ \theta_{b} \le \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \theta_{b} \text{ exists} \wedge t \ge 3 \]

first#

a position in a dimension, and the same position within a group

first:
  dims: [snapshot, generator]
  where: "position(snapshot) == 0 OR position(season_of.snapshot, within=season) == 0"
  expression: on == 1
\[ \mathit{on}_{t,g} = 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{pos}(t) = 0 \vee \mathrm{pos}_{\mathrm{season\_of}(t)}(t) = 0 \]

last#

the same two counted from the end, which print against a size rather than as themselves

last:
  dims: [snapshot, generator]
  where: "position(snapshot) == -1 OR position(season_of.snapshot, within=season) == -1"
  expression: on == 0
\[ \mathit{on}_{t,g} = 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{pos}(t) = \lvert \mathcal{T} \rvert - 1 \vee \mathrm{pos}_{\mathrm{season\_of}(t)}(t) = \lvert \mathcal{T}_{\mathrm{season\_of}(t)} \rvert - 1 \]

northern#

a relation compared to a label, to another relation, and to nothing

northern:
  dims: [snapshot, bus]
  where: "zone_of == 'north' AND zone_of != area_of AND zone_of"
  expression: slack <= load
\[ \mathit{slack}_{t} \le \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \mathrm{zone\_of}(b) = \text{'}\mathrm{north}\text{'} \wedge \mathrm{zone\_of}(b) \neq \mathrm{area\_of}(b) \wedge \mathrm{zone\_of}(b) \text{ is defined} \]

efficiency#

a Greek-named parameter, which is given — so the convention wins and it prints as the word

efficiency:
  dims: [snapshot, generator]
  expression: p <= eta * p_max
\[ p_{t,g} \le \mathrm{eta}_{g} \cdot \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

ceiling#

the infinity literal, which is the one way infinity prints

ceiling:
  dims: [bus]
  expression: theta <= inf
\[ \theta_{b} \le \infty \qquad \forall\, b \in \mathcal{B} \]

always#

a mask that is only the constant true, which the language says is no mask at all — so none prints

always:
  dims: [snapshot]
  where: "true"
  expression: spill >= 0
\[ \mathit{spill}_{t} \ge 0 \qquad \forall\, t \in \mathcal{T} \]

redundant#

the same constant inside a mask, where it is what the file says and prints

redundant:
  dims: [snapshot]
  where: "True AND spill"
  expression: spill >= 0
\[ \mathit{spill}_{t} \ge 0 \qquad \forall\, t \in \mathcal{T} \,:\, \mathit{spill}_{t} \text{ exists} \]

never#

the other constant mask, which says the rows are none and is worth seeing

never:
  dims: [snapshot]
  where: "false"
  expression: slack >= 0
\[ \mathit{slack}_{t} \ge 0 \qquad \forall\, t \in \mathcal{T} \,:\, \bot \]

Definitions#

spend#

a plain named expression: its symbol prints where it is used, its body once as a definition

spend:
  expression: sum(p * cost, over=generator)
\[ \mathit{spend}_{t} = \sum_{g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} \qquad \forall\, t \in \mathcal{T} \]

lcoe#

nothing in the math reads it, so its divisor may carry a variable

lcoe: sum(p * cost) / sum(p)
\[ \mathit{lcoe} = \frac{\sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g}}{\sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g}} \]

marginal_price#

the row dual of a constraint, the one builtin only an entry the math never reads may call

marginal_price: dual(balance)
\[ \mathit{marginal\_price}_{t,b} = \lambda_{\mathrm{balance},t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \]

startup_cost#

a quantity defined by region: no two cases overlap, and otherwise is the rest

startup_cost:
  dims: [snapshot, generator]
  cases:
    opening: { when: "position(snapshot) == 0", expression: cost }
    winter: { when: "position(snapshot) > 0 and season_of == 'winter'", expression: cost * 2 }
  otherwise: 0
\[ \mathrm{startup\_cost}_{t,g} = \begin{cases} \mathrm{cost}_{g} & \text{if } \mathrm{pos}(t) = 0 \\ \mathrm{cost}_{g} \cdot 2 & \text{if } \mathrm{pos}(t) > 0 \wedge \mathrm{season\_of}(t) = \text{'}\mathrm{winter}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Variable domains#

p#

both bounds, and a where with all three connectives

p:
  dims: [snapshot, generator]
  where: "p_max > 0 AND NOT is_flexible OR p_min > 0"
  bounds: { lower: p_min, upper: p_max }
\[ \mathrm{p}^{\mathrm{min}}_{g} \le p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{p}^{\mathrm{max}}_{g} > 0 \wedge \neg \mathrm{is\_flexible}_{g} \vee \mathrm{p}^{\mathrm{min}}_{g} > 0 \]

spill#

lower only

spill:
  dims: [snapshot]
  bounds: { lower: 0 }
\[ \mathit{spill}_{t} \ge 0 \qquad \forall\, t \in \mathcal{T} \]

slack#

upper only

slack:
  dims: [snapshot]
  bounds: { upper: 100 }
\[ \mathit{slack}_{t} \le 100 \qquad \forall\, t \in \mathcal{T} \]

theta#

unbounded

theta:
  dims: [bus]
\[ \theta_{b} \in \mathbb{R} \qquad \forall\, b \in \mathcal{B} \]

on#

a binary domain, which is a set rather than a pair of bounds

on:
  dims: [snapshot, generator]
  domain: binary
\[ \mathit{on}_{t,g} \in \{0, 1\} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

units#

an integer domain, which is both: bounds, and where the values live

units:
  dims: [generator]
  domain: integer
  bounds: { lower: 0, upper: 10 }
\[ 0 \le \mathit{units}_{g} \le 10, \mathit{units}_{g} \in \mathbb{Z} \qquad \forall\, g \in \mathcal{G} \]

spare#

integer with neither bound: the domain is the whole line

spare:
  dims: [generator]
  domain: integer
\[ \mathit{spare}_{g} \in \mathbb{Z} \qquad \forall\, g \in \mathcal{G} \]

reserve#

an empty dims: a scalar declaration, whose line carries no quantifier

reserve:
  dims: []
  bounds: { lower: 0 }
\[ \mathit{reserve} \ge 0 \]

headroom#

scalar too, but masked, so the condition stands with no set beside it

headroom:
  dims: []
  where: "budget"
  bounds: { lower: 0 }
\[ \mathit{headroom} \ge 0 \qquad \text{where } \mathrm{budget} \text{ is defined} \]

weight#

the family a sos runs along

weight:
  dims: [snapshot, generator]
  bounds: { lower: 0, upper: 1 }
\[ 0 \le \mathit{weight}_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Curves, as what they expand to#

A curve is sugar: what prints is the formulation it expands to, which is the math the solver receives. One row per method:, each from the model named under it, so the symbols in this section are that model's.

economies_of_scale#

method: adjacency — a binary per segment, and a row making the two nonzero weights neighbours, in examples/ports/transport_pwl.yaml.

Rendered with the sidecar symbol table examples/symbols/transport_pwl.yaml, which is what the weights print as:

notation: latex

names:
  economies_of_scale_lam: "\\lambda"
  economies_of_scale_seg: "\\delta"
  bp_x: "\\mathrm{x}"
  bp_y: "\\mathrm{y}"
economies_of_scale:
  over: bp
  links:
    - [shipment, bp_x]
    - [scaled, bp_y]
\[ \sum_{b \in \mathcal{B}} \lambda_{p,m,b} = 1 \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M} \]
\[ \mathit{shipment}_{p,m} = \sum_{b \in \mathcal{B}} \lambda_{p,m,b} \cdot \mathrm{x}_{b} \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M} \]
\[ \mathit{scaled}_{p,m} = \sum_{b \in \mathcal{B}} \lambda_{p,m,b} \cdot \mathrm{y}_{b} \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M} \]
\[ \sum_{b \in \mathcal{B}} \delta_{p,m,b} = 1 \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M} \]
\[ \lambda_{p,m,b} \le \delta_{p,m,b} + \delta_{p,m,b \boxminus_{0} 1} \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M},\ b \in \mathcal{B} \]
\[ 0 \le \lambda_{p,m,b} \le 1 \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M},\ b \in \mathcal{B} \]
\[ \delta_{p,m,b} \in \{0, 1\} \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M},\ b \in \mathcal{B} \]

cost_curve#

method: sos2 — the same weights, restricted by a set the solver branches on (the sos rules), in examples/sos.yaml.

Rendered with the sidecar symbol table examples/symbols/sos.yaml, which is what the weights print as:

notation: latex

names:
  cost_curve_lam: "\\lambda"
  bp_x: "\\mathrm{x}"
  bp_y: "\\mathrm{y}"
cost_curve:
  over: bp
  links:
    - [dispatch, bp_x]
    - [op_cost, bp_y]
  method: sos2
\[ \sum_{b \in \mathcal{B}} \lambda_{t,g,b} = 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ \mathit{dispatch}_{t,g} = \sum_{b \in \mathcal{B}} \lambda_{t,g,b} \cdot \mathrm{x}_{g,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ \mathit{op\_cost}_{t,g} = \sum_{b \in \mathcal{B}} \lambda_{t,g,b} \cdot \mathrm{y}_{g,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ 0 \le \lambda_{t,g,b} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G},\ b \in \mathcal{B} \]
\[ \left( \lambda_{t,g,b} \right)_{b \in \mathcal{B}} \in \mathrm{SOS}2 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

cost_curve#

method: convex — nothing — the weights range over the hull, which is a pure LP, in examples/piecewise.yaml.

Rendered with the sidecar symbol table examples/symbols/piecewise.yaml, which is what the weights print as:

notation: latex

names:
  cost_curve_lam: "\\lambda"
  bp_x: "\\mathrm{x}"
  bp_y: "\\mathrm{y}"
cost_curve:
  over: bp
  links:
    - [dispatch, bp_x]
    - [op_cost, bp_y]
  method: convex
\[ \sum_{b \in \mathcal{B}} \lambda_{t,g,b} = 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ \mathit{dispatch}_{t,g} = \sum_{b \in \mathcal{B}} \lambda_{t,g,b} \cdot \mathrm{x}_{g,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ \mathit{op\_cost}_{t,g} = \sum_{b \in \mathcal{B}} \lambda_{t,g,b} \cdot \mathrm{y}_{g,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ 0 \le \lambda_{t,g,b} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G},\ b \in \mathcal{B} \]

cost_curve#

method: lp — no weights at all — one row per segment line, plus the two rows holding the domain, in examples/piecewise_lp.yaml.

Rendered with the sidecar symbol table examples/symbols/piecewise_lp.yaml, which is what the weights print as:

notation: latex

names:
  bp_x: "\\mathrm{x}"
  bp_y: "\\mathrm{y}"
cost_curve:
  over: bp
  links:
    - [dispatch, bp_x]
    - [op_cost, bp_y, ">="]
  method: lp
\[ \mathit{op\_cost}_{t,g} \cdot \left( \mathrm{x}_{g,b} - \mathrm{x}_{g,b \boxminus_{0} 1} \right) \ge \left( \mathrm{y}_{g,b} - \mathrm{y}_{g,b \boxminus_{0} 1} \right) \cdot \left( \mathit{dispatch}_{t,g} - \mathrm{x}_{g,b} \right) + \mathrm{y}_{g,b} \cdot \left( \mathrm{x}_{g,b} - \mathrm{x}_{g,b \boxminus_{0} 1} \right) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \mathrm{pos}(b) \neq 0 \]
\[ \mathit{dispatch}_{t,g} \ge \mathrm{x}_{g,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \mathrm{pos}(b) = 0 \]
\[ \mathit{dispatch}_{t,g} \le \mathrm{x}_{g,b} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \mathrm{pos}(b) = \lvert \mathcal{B} \rvert - 1 \]

Sets carried to the solver#

adjacent#

at most two adjacent members nonzero, one set per snapshot

adjacent:
  variable: weight
  over: generator
  type: 2
\[ \left( \mathit{weight}_{t,g} \right)_{g \in \mathcal{G}} \in \mathrm{SOS}2 \qquad \forall\, t \in \mathcal{T} \]