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 are the derived ones, taken with no symbol table, because that is what a model prints with no setup — \(\mathit{load}_{t}\) rather than \(\ell_t\). A symbol table replaces them wholesale and changes nothing else on this page.

The legend#

A dimension, a lookup 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 }

lookups:
  gen_bus: { over: generator, into: bus }
  gen_tech: { over: generator, into: technology } # a second map out of `generator`, to group through both at once
  zone_of: { over: bus, into: zone }
  area_of: { over: bus, into: zone } # a second map into the same set, to compare against
  season_of: { over: snapshot, into: season }
  tech: { over: generator, dtype: str } # no `into`: a label space, which the legend words differently

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}\)
\(\mathcal{G}\) index \(g\)generator with \(\mathrm{gen\_bus}: \mathcal{G} \to \mathcal{B},\enspace \mathrm{gen\_tech}: \mathcal{G} \to \mathcal{E}\) carrying label \(\mathrm{tech}\)
\(\mathcal{B}\) index \(b\)bus with \(\mathrm{zone\_of}: \mathcal{B} \to \mathcal{Z},\enspace \mathrm{area\_of}: \mathcal{B} \to \mathcal{Z}\)
\(\mathcal{Z}\) index \(z\)zone
\(\mathcal{S}\) index \(s\)season
\(\mathcal{E}\) index \(e\)technology

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

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{lookup}(t)} k\) denotes a translation counted inside the group a lookup puts \(t\) in (shift(by=lookup)), 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{lookup}(t)} k\) is both at once.

\(\mathrm{pos}(t)\) denotes where index \(t\) sits along its dimension's own order — the order shift walks, 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{lookup}(t)}(t)\) counts within the group a lookup puts \(t\) in: the subscript names the map, \(\mathcal{T}_{\mathrm{lookup}(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},\enspace g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} + \sum_{t \in \mathcal{T},\enspace g \in \mathcal{G}} p_{t,g} \cdot p_{t,g} \cdot \mathrm{cost}_{g} + \sum_{t \in \mathcal{T},\enspace g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} \cdot \mathrm{growth}^{\mathrm{lead}_{g}} + \sum_{t \in \mathcal{T},\enspace g \in \mathcal{G}} p_{t,g} \cdot \left( \mathrm{growth}^{\mathrm{lead}_{g}} \right)^{2} + \sum_{t \in \mathcal{T},\enspace g \in \mathcal{G}} p_{t,g} \cdot \mathrm{p}^{\mathrm{max}}_{g} - \mathit{reserve} - \mathit{headroom}\]

Constraints#

starts#

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

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

balance#

sum over a lookup

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

ramp#

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

ramp:
  foreach: [snapshot, generator]
  expression: p - shift(p, over=snapshot, offset=1, edge='wrap') <= shift(p, over=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\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]

edges#

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

edges:
  foreach: [snapshot, generator]
  expression: >-
    shift(p, over=snapshot, offset=1, edge=0)
    <= shift(p, over=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\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]

ahead#

the cyclic translation forwards, which is a fourth symbol again

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

composed#

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

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

uncomposed#

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

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

crossed#

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

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

lead_time#

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

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

in_season#

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

in_season:
  foreach: [snapshot, generator]
  expression: p <= shift(p, over=snapshot, offset=1, edge='wrap', by=season_of)
\[p_{t,g} \le p_{t \ominus^{\mathrm{season\_of}(t)} 1,g} \qquad \forall\thinspace t \in \mathcal{T},\enspace 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:
  foreach: [snapshot, generator]
  expression: p <= shift(p, over=snapshot, offset=1, edge=0, by=season_of)
\[p_{t,g} \le p_{t \boxminus_{0}^{\mathrm{season\_of}(t)} 1,g} \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]

window#

a trailing window of fixed width

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

history#

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

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

seasonal_window#

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

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

pullback#

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

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

grouped_twice#

one grouping through two maps: the domain carries both conditions

grouped_twice:
  foreach: [snapshot, bus, technology]
  expression: sum(p, by=[gen_bus, gen_tech]) <= tech_cap
\[\sum_{g \in \mathcal{G} \thinspace:\thinspace \mathrm{gen\_bus}(g) = b \wedge \mathrm{gen\_tech}(g) = e} p_{t,g} \le \mathrm{tech\_cap}_{b,e} \qquad \forall\thinspace t \in \mathcal{T},\enspace b \in \mathcal{B},\enspace e \in \mathcal{E}\]

pulled_back_twice#

its adjoint, reading one slot through a pair of labels

pulled_back_twice:
  foreach: [generator]
  expression: units <= at(tech_cap, by=[gen_bus, gen_tech])
\[\mathit{units}_{g} \le \mathrm{tech\_cap}_{\mathrm{gen\_bus}(g),\mathrm{gen\_tech}(g)} \qquad \forall\thinspace g \in \mathcal{G}\]

arithmetic#

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

arithmetic:
  foreach: [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\thinspace t \in \mathcal{T}\]

total#

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

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

scalar#

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

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

running#

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

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

first#

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

first:
  foreach: [snapshot, generator]
  where: "position(snapshot) == 0 OR position(snapshot, by=season_of) == 0"
  expression: on == 1
\[\mathit{on}_{t,g} = 1 \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G} \thinspace:\thinspace \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:
  foreach: [snapshot, generator]
  where: "position(snapshot) == -1 OR position(snapshot, by=season_of) == -1"
  expression: on == 0
\[\mathit{on}_{t,g} = 0 \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G} \thinspace:\thinspace \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 lookup compared to a label, to another lookup, and to nothing

northern:
  foreach: [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\thinspace t \in \mathcal{T},\enspace b \in \mathcal{B} \thinspace:\thinspace \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:
  foreach: [snapshot, generator]
  expression: p <= eta * p_max
\[p_{t,g} \le \mathrm{eta}_{g} \cdot \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]

ceiling#

the infinity literal, which is the one way infinity prints

ceiling:
  foreach: [bus]
  expression: theta <= inf
\[\theta_{b} \le \infty \qquad \forall\thinspace 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:
  foreach: [snapshot]
  where: "true"
  expression: spill >= 0
\[\mathit{spill}_{t} \ge 0 \qquad \forall\thinspace t \in \mathcal{T}\]

redundant#

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

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

never#

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

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

Definitions#

lcoe#

nothing in the math reads it, so it prints under its own name rather than inlining — and its divisor may carry a variable

lcoe: sum(p * cost) / sum(p)
\[\mathit{lcoe} = \frac{\sum_{t \in \mathcal{T},\enspace g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g}}{\sum_{t \in \mathcal{T},\enspace 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\thinspace t \in \mathcal{T},\enspace b \in \mathcal{B}\]

startup_cost#

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

startup_cost:
  foreach: [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 \cr \mathrm{cost}_{g} \cdot 2 & \text{if } \mathrm{pos}(t) > 0 \wedge \mathrm{season\_of}(t) = \text{'}\mathrm{winter}\text{'} \cr 0 & \text{otherwise} \end{cases} \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]

Variable domains#

p#

both bounds, and a where with all three connectives

p:
  foreach: [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\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G} \thinspace:\thinspace \mathrm{p}^{\mathrm{max}}_{g} > 0 \wedge \neg \mathrm{is\_flexible}_{g} \vee \mathrm{p}^{\mathrm{min}}_{g} > 0\]

spill#

lower only

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

slack#

upper only

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

theta#

unbounded

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

on#

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

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

units#

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

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

spare#

integer with neither bound: the domain is the whole line

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

reserve#

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

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

headroom#

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

headroom:
  foreach: []
  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:
  foreach: [snapshot, generator]
  bounds: { lower: 0, upper: 1 }
\[0 \le \mathit{weight}_{t,g} \le 1 \qquad \forall\thinspace t \in \mathcal{T},\enspace 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\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M}\]
\[\mathit{shipment}_{p,m} = \sum_{b \in \mathcal{B}} \lambda_{p,m,b} \cdot \mathrm{x}_{b} \qquad \forall\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M}\]
\[\mathit{scaled}_{p,m} = \sum_{b \in \mathcal{B}} \lambda_{p,m,b} \cdot \mathrm{y}_{b} \qquad \forall\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M}\]
\[\sum_{b \in \mathcal{B}} \delta_{p,m,b} = 1 \qquad \forall\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M}\]
\[\lambda_{p,m,b} \le \delta_{p,m,b} + \delta_{p,m,b \boxminus_{0} 1} \qquad \forall\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M},\enspace b \in \mathcal{B}\]
\[0 \le \lambda_{p,m,b} \le 1 \qquad \forall\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M},\enspace b \in \mathcal{B}\]
\[\delta_{p,m,b} \in \{0, 1\} \qquad \forall\thinspace p \in \mathcal{P},\enspace m \in \mathcal{M},\enspace 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:
    - [p, bp_x]
    - [op_cost, bp_y]
  method: sos2
\[\sum_{b \in \mathcal{B}} \lambda_{t,g,b} = 1 \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]
\[p_{t,g} = \sum_{b \in \mathcal{B}} \lambda_{t,g,b} \cdot \mathrm{x}_{g,b} \qquad \forall\thinspace t \in \mathcal{T},\enspace 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\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]
\[0 \le \lambda_{t,g,b} \le 1 \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G},\enspace b \in \mathcal{B}\]
\[\left( \lambda_{t,g,b} \right)_{b \in \mathcal{B}} \in \mathrm{SOS}2 \qquad \forall\thinspace t \in \mathcal{T},\enspace 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:
    - [p, bp_x]
    - [op_cost, bp_y]
  method: convex
\[\sum_{b \in \mathcal{B}} \lambda_{t,g,b} = 1 \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]
\[p_{t,g} = \sum_{b \in \mathcal{B}} \lambda_{t,g,b} \cdot \mathrm{x}_{g,b} \qquad \forall\thinspace t \in \mathcal{T},\enspace 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\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}\]
\[0 \le \lambda_{t,g,b} \le 1 \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G},\enspace 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:
    - [p, 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( p_{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\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G},\enspace b \in \mathcal{B} \thinspace:\thinspace \mathrm{pos}(b) \neq 0\]
\[p_{t,g} \ge \mathrm{x}_{g,b} \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G},\enspace b \in \mathcal{B} \thinspace:\thinspace \mathrm{pos}(b) = 0\]
\[p_{t,g} \le \mathrm{x}_{g,b} \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G},\enspace b \in \mathcal{B} \thinspace:\thinspace \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\thinspace t \in \mathcal{T}\]