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 }
  bp: { dtype: int } # the breakpoints every curve below runs through

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
  bp_x: { dims: [generator, bp] } # the x-axis of every curve below, and what a derived mask is read from
  bp_y: { dims: [generator, bp] }
  bp_heat: { dims: [generator, bp] }
  bp_run: { dims: [generator, bp], dtype: bool } # how far each curve runs, so a block has a mask to print

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}\)
\(\mathcal{A}\) index \(a\) — bp

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)
\(\mathrm{bp\_x}\) bp_x over \(\mathcal{G} \times \mathcal{A}\)
\(\mathrm{bp\_y}\) bp_y over \(\mathcal{G} \times \mathcal{A}\)
\(\mathrm{bp\_heat}\) bp_heat over \(\mathcal{G} \times \mathcal{A}\)
\(\mathrm{bp\_run}\) bp_run over \(\mathcal{G} \times \mathcal{A}\)

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}\)
\(\mathit{fuel}\) fuel over \(\mathcal{T} \times \mathcal{G}\)
\(\mathit{heat}\) heat over \(\mathcal{T} \times \mathcal{G}\)
\(\mathit{op\_cost}\) op_cost over \(\mathcal{T} \times \mathcal{G}\)
\(\mathit{warm}\) warm over \(\mathcal{T} \times \mathcal{G}\)

Definitions#

Symbol Meaning
\(\mathrm{spend}^{\mathrm{cap}}\) spend_cap over \(\mathcal{G}\)
\(\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, by=gen_bus, over=generator, into=bus) + 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=snapshot, offset=1, edge='wrap', by=season_of, 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=snapshot, offset=1, edge=0, by=season_of, 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=snapshot, window=3, by=season_of, 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, into=[bus, technology], over=generator) <= 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(generator, by=gen_bt, within=[bus, technology]) == 0"
  expression: units <= shift(units, along=generator, offset=1, edge=0, by=gen_bt, 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, over=generator, into=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, by=rep_of, over=snapshot, into=rep) <= 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, by=gen_zone, over=generator, into=zone) <= 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, by=gen_zone, over=snapshot, into=zone) <= 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, over=generator, into=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(generator, by=gen_zone, 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(snapshot, by=season_of, 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(snapshot, by=season_of, 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 \]

margin#

a mask comparing two expressions, which prints as the arithmetic it is

margin:
  dims: [snapshot, generator]
  where: "p_max - p_min > cost / 2"
  expression: p <= p_max
\[ p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{p}^{\mathrm{max}}_{g} - \mathrm{p}^{\mathrm{min}}_{g} > \frac{\mathrm{cost}_{g}}{2} \]

ramped#

a translation under a comparison names its edge, a pullback reads through a relation, and the position keeps the vacated row out

ramped:
  dims: [snapshot, bus]
  where: "load - shift(load, along=snapshot, offset=1, edge=0) <= at(zone_cap, by=zone_of, over=zone, into=bus) AND position(snapshot) > 0"
  expression: slack <= load
\[ \mathit{slack}_{t} \le \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \mathrm{load}_{t,b} - \mathrm{load}_{t \boxminus_{0} 1,b} \le \mathrm{zone\_cap}_{\mathrm{zone\_of}(b)} \wedge \mathrm{pos}(t) > 0 \]

covered#

a reduction on a side of a scalar mask, so nothing is left to quantify

covered:
  dims: []
  where: "sum(p_max, over=generator) >= budget"
  expression: sum(p) <= budget
\[ \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \le \mathrm{budget} \qquad \text{where } \sum_{g \in \mathcal{G}} \mathrm{p}^{\mathrm{max}}_{g} \ge \mathrm{budget} \]

counted#

a count of the coordinates a predicate admits, which reduces one dim away

counted:
  dims: [bus]
  where: "count(tech_cap > 0, over=technology) >= 2"
  expression: theta <= budget
\[ \theta_{b} \le \mathrm{budget} \qquad \forall\, b \in \mathcal{B} \,:\, \lvert \{ e \in \mathcal{E} \,:\, \mathrm{tech\_cap}_{b,e} > 0 \} \rvert \ge 2 \]

counted_here#

the same count along a dim the frame carries, so the set takes a primed dummy

counted_here:
  dims: [bus, technology]
  where: "count(tech_cap > 0, over=technology) >= 2"
  expression: theta <= tech_cap
\[ \theta_{b} \le \mathrm{tech\_cap}_{b,e} \qquad \forall\, b \in \mathcal{B},\ e \in \mathcal{E} \,:\, \lvert \{ e' \in \mathcal{E} \,:\, \mathrm{tech\_cap}_{b,e'} > 0 \} \rvert \ge 2 \]

run_start#

a predicate read one coordinate back, which is false where the translation vacates

run_start:
  dims: [snapshot, bus]
  where: "load AND NOT shift(load, along=snapshot, offset=1)"
  expression: slack <= load
\[ \mathit{slack}_{t} \le \mathrm{load}_{t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \mathrm{load}_{t,b} \text{ is defined} \wedge \neg \left( \mathrm{load}_{t - 1,b} \text{ is defined} \right) \]

zoned#

a predicate read through a relation: a bus is held only where its zone has a cap at all

zoned:
  dims: [bus]
  where: "at(zone_cap, by=zone_of, over=zone, into=bus)"
  expression: theta <= budget
\[ \theta_{b} \le \mathrm{budget} \qquad \forall\, b \in \mathcal{B} \,:\, \mathrm{zone\_cap}_{\mathrm{zone\_of}(b)} \text{ is defined} \]

capped#

an expressions: entry on a side, read by the name the file gave it

capped:
  dims: [snapshot, generator]
  where: "spend_cap > 0 OR NOT is_flexible"
  expression: p <= p_max
\[ p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{spend}^{\mathrm{cap}}_{g} > 0 \vee \neg \mathrm{is\_flexible}_{g} \]

Definitions#

spend_cap#

a data-only entry, so a where may compare it

spend_cap: cost * 2
\[ \mathrm{spend}^{\mathrm{cap}}_{g} = \mathrm{cost}_{g} \cdot 2 \qquad \forall\, g \in \mathcal{G} \]

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} \]

fuel#

a curve's second axis

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

heat#

its third, so one curve ties three expressions

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

op_cost#

bounded by a curve rather than pinned to it

op_cost:
  dims: [snapshot, generator]
  bounds: { lower: 0 }
\[ \mathit{op\_cost}_{t,g} \ge 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

warm#

a gate not every unit has, so the curve it gates is ungated where it does not exist

warm:
  dims: [snapshot, generator]
  domain: binary
  where: "is_flexible"
\[ \mathit{warm}_{t,g} \in \{0, 1\} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{is\_flexible}_{g} \]

Curves#

A curve prints as the curve it states, over the frame the block builds one per coordinate of, and its expansion prints the rows that curve stands for. 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 breakpoints 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]
\[ \left( \mathit{shipment}_{p,m},\ \mathit{scaled}_{p,m} \right) \in \mathrm{pwl}_{b \in \mathcal{B}}(\mathrm{x}_{b},\ \mathrm{y}_{b}) \qquad \forall\, p \in \mathcal{P},\ m \in \mathcal{M} \]

Written out by spec.expand():

\[ \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} \le 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} \]
\[ \mathrm{x}_{b} \text{ is defined} \wedge \mathrm{y}_{b} \text{ is defined} \qquad \forall\, 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 breakpoints 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
\[ \left( \mathit{dispatch}_{t,g},\ \mathit{op\_cost}_{t,g} \right) \in \mathrm{pwl}_{b \in \mathcal{B}}(\mathrm{x}_{g,b},\ \mathrm{y}_{g,b}) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Written out by spec.expand():

\[ \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} \]
\[ \mathrm{x}_{g,b} \text{ is defined} \wedge \mathrm{y}_{g,b} \text{ is defined} \qquad \forall\, g \in \mathcal{G},\ b \in \mathcal{B} \]

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 breakpoints 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
\[ \left( \mathit{dispatch}_{t,g},\ \mathit{op\_cost}_{t,g} \right) \in \mathrm{conv}_{b \in \mathcal{B}}(\mathrm{x}_{g,b},\ \mathrm{y}_{g,b}) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Written out by spec.expand():

\[ \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} \]
\[ \mathrm{x}_{g,b} \text{ is defined} \wedge \mathrm{y}_{g,b} \text{ is defined} \qquad \forall\, g \in \mathcal{G},\ b \in \mathcal{B} \]
\[ \mathrm{x}_{g,b \boxminus_{0} 1} < \mathrm{x}_{g,b} \qquad \forall\, g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \mathrm{pos}(b) > 0 \]
\[ \lvert \{ b \in \mathcal{B} \,:\, \left( \mathrm{y}_{g,b} - \mathrm{y}_{g,b \boxminus_{0} 1} \right) \cdot \left( \mathrm{x}_{g,b \boxplus_{0} 1} - \mathrm{x}_{g,b} \right) > \left( \mathrm{y}_{g,b \boxplus_{0} 1} - \mathrm{y}_{g,b} \right) \cdot \left( \mathrm{x}_{g,b} - \mathrm{x}_{g,b \boxminus_{0} 1} \right) \wedge \mathrm{pos}(b) > 0 \wedge \mathrm{pos}(b) \neq \lvert \mathcal{B} \rvert - 1 \} \rvert = 0 \vee \lvert \{ b \in \mathcal{B} \,:\, \left( \mathrm{y}_{g,b} - \mathrm{y}_{g,b \boxminus_{0} 1} \right) \cdot \left( \mathrm{x}_{g,b \boxplus_{0} 1} - \mathrm{x}_{g,b} \right) < \left( \mathrm{y}_{g,b \boxplus_{0} 1} - \mathrm{y}_{g,b} \right) \cdot \left( \mathrm{x}_{g,b} - \mathrm{x}_{g,b \boxminus_{0} 1} \right) \wedge \mathrm{pos}(b) > 0 \wedge \mathrm{pos}(b) \neq \lvert \mathcal{B} \rvert - 1 \} \rvert = 0 \qquad \forall\, g \in \mathcal{G} \]

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 breakpoints 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} \ge \mathrm{pwl}_{b \in \mathcal{B}}(\mathrm{x}_{g,b},\ \mathrm{y}_{g,b})(\mathit{dispatch}_{t,g}) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Written out by spec.expand():

\[ \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) > 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 \]
\[ \mathrm{x}_{g,b} \text{ is defined} \wedge \mathrm{y}_{g,b} \text{ is defined} \qquad \forall\, g \in \mathcal{G},\ b \in \mathcal{B} \]
\[ \mathrm{x}_{g,b \boxminus_{0} 1} < \mathrm{x}_{g,b} \qquad \forall\, g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \mathrm{pos}(b) > 0 \]
\[ \left( \mathrm{y}_{g,b} - \mathrm{y}_{g,b \boxminus_{0} 1} \right) \cdot \left( \mathrm{x}_{g,b \boxplus_{0} 1} - \mathrm{x}_{g,b} \right) \le \left( \mathrm{y}_{g,b \boxplus_{0} 1} - \mathrm{y}_{g,b} \right) \cdot \left( \mathrm{x}_{g,b} - \mathrm{x}_{g,b \boxminus_{0} 1} \right) \qquad \forall\, g \in \mathcal{G},\ b \in \mathcal{B} \,:\, \mathrm{pos}(b) > 0 \wedge \mathrm{pos}(b) \neq \lvert \mathcal{B} \rvert - 1 \]
\[ \lvert \{ b \in \mathcal{B} \,:\, \mathrm{x}_{g,b} \text{ is defined} \} \rvert \ge 2 \qquad \forall\, g \in \mathcal{G} \]

Sets carried to the solver#

A set prints beside the variable it restricts, because it restricts that variable rather than adding a row of its own. Under it are the rows it is written out as.

adjacent#

at most two adjacent members nonzero, one set per snapshot

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

Written out by spec.expand():

\[ \sum_{g \in \mathcal{G}} \mathit{adjacent\_seg}_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T} \]
\[ \mathit{weight}_{t,g} \le \mathit{adjacent\_seg}_{t,g} + \mathit{adjacent\_seg}_{t,g \boxminus_{0} 1} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]
\[ \mathit{adjacent\_seg}_{t,g} \in \{0, 1\} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

What the data has to satisfy#

bounds_do_not_cross#

two parameters, which is arithmetic like any other

bounds_do_not_cross: "p_min <= p_max"
\[ \mathrm{p}^{\mathrm{min}}_{g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, g \in \mathcal{G} \]

efficiency_is_a_fraction#

a connective, so the line has no relation to align on

efficiency_is_a_fraction: "eta > 0 AND eta <= 1"
\[ \mathrm{eta}_{g} > 0 \wedge \mathrm{eta}_{g} \le 1 \qquad \forall\, g \in \mathcal{G} \]

lead_times_are_short#

one parameter against a literal

lead_times_are_short: "lead <= 3"
\[ \mathrm{lead}_{g} \le 3 \qquad \forall\, g \in \mathcal{G} \]

zones_agree#

two maps into one set, compared row by row

zones_agree: "zone_of == area_of"
\[ \mathrm{zone\_of}(b) = \mathrm{area\_of}(b) \qquad \forall\, b \in \mathcal{B} \]

budget_covers_the_peak#

a reduction on a side, leaving nothing to quantify

budget_covers_the_peak: "sum(p_max, over=generator) >= budget"
\[ \sum_{g \in \mathcal{G}} \mathrm{p}^{\mathrm{max}}_{g} \ge \mathrm{budget} \]

ramps_are_gentle#

a translation inside arithmetic, and a position keeping the vacated row out

ramps_are_gentle:
  holds: "load - shift(load, along=snapshot, offset=1, edge=0) <= budget"
  where: "position(snapshot) > 0"
\[ \mathrm{load}_{t,b} - \mathrm{load}_{t \boxminus_{0} 1,b} \le \mathrm{budget} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \mathrm{pos}(t) > 0 \]

flexible_units_have_headroom#

a bare bool parameter as the where

flexible_units_have_headroom:
  holds: "p_min < p_max"
  where: "is_flexible"
\[ \mathrm{p}^{\mathrm{min}}_{g} < \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{is\_flexible}_{g} \]

northern_demand_is_real#

a relation comparison as the where, over a frame two dims wide

northern_demand_is_real:
  holds: "load >= 0"
  where: "zone_of == 'north'"
\[ \mathrm{load}_{t,b} \ge 0 \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \mathrm{zone\_of}(b) = \text{'}\mathrm{north}\text{'} \]