Every construct, as math#
This page shows every construct of the language beside the math that the typesetter prints for it. Use it to find how a construct prints, or which construct printed a symbol.
Each section shows the YAML of one construct, then its equation. Most fragments
come from one test model,
tests/typesetting/golden/model.yaml,
which holds every construct and is not a sensible model. The curves come from
the example models that their section names. What each operator does is on
Operators.
The symbols are derived from the names in the file, so you see \(\mathrm{load}_{t}\) rather than \(\ell_t\). A symbol table replaces the symbols and changes nothing else.
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\).
Variable domains#
Lower and upper bounds#
both bounds, and a where with all three connectives
variables:
p:
dims: [snapshot, generator]
where: "p_max > 0 AND NOT is_flexible OR p_min > 0"
bounds: { lower: p_min, upper: p_max }
Lower bound only#
Upper bound only#
Unbounded variable#
Binary domain#
a binary domain, which is a set rather than a pair of bounds
Bounded integer domain#
an integer domain, which is both: bounds, and where the values live
Unbounded integer domain#
integer with neither bound: the domain is the whole line
Scalar variable#
an empty dims: a scalar declaration, whose line carries no quantifier
Scalar variable with a condition#
scalar too, but masked, so the condition stands with no set beside it
Variable in a special ordered set#
the family a sos runs along
Second axis of a curve#
a curve's second axis
Third axis of a curve#
its third, so one curve ties three expressions
Variable bounded by a curve#
bounded by a curve rather than pinned to it
Binary variable with a condition#
a gate not every unit has, so the curve it gates is ungated where it does not exist
Objective#
Products and powers in the 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
objective:
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
Relations#
Sum through a relation#
constraints:
balance:
dims: [snapshot, bus]
expression: sum(p, by=gen_bus, over=generator, into=bus) + spill - slack == load
Sum over every dimension#
a sum naming no dim, whose domain is the one place the dims it took are said
at through a relation#
at(), which re-indexes through a relation instead of an offset
constraints:
pullback:
dims: [snapshot, bus]
expression: spill <= at(zone_cap, by=zone_of, over=zone, into=bus)
Sum into two value columns#
one table read to two value columns: the domain carries a condition per column
constraints:
grouped_once:
dims: [snapshot, bus, technology]
expression: sum(p, by=gen_bt, into=[bus, technology], over=generator) <= tech_cap
at through two value columns#
its adjoint, reading one slot through two columns of one table
constraints:
pulled_back_once:
dims: [generator]
expression: units <= at(tech_cap, by=gen_bt, over=[bus, technology], into=generator)
Shift within one value column#
a partition grouped by one named value column of a two-value table, and a position within both
constraints:
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)
Sum through a bare relation#
a sum through a bare relation: the domain is a row of the relation rather than a function's value
constraints:
relational:
dims: [snapshot, bus]
expression: sum(p, by=connection, over=generator, into=bus) <= load
Bare relation as a condition#
a bare relation as a where: the row of the frame has to be a member of the relation
Map into its own dimension#
a map into its own dimension, read both ways: the frame is unchanged and the index is primed
constraints:
representative:
dims: [snapshot]
expression: sum(spill, by=rep_of, over=snapshot, into=rep) <= at(spill, by=rep_of, over=rep, into=snapshot)
Sum through a two-key map#
a grouping through a two-key map, consuming one key: the condition reads the other, and the row keeps it
constraints:
zonal:
dims: [snapshot, zone]
expression: sum(p, by=gen_zone, over=generator, into=zone) <= zone_cap
Sum over the other key of a two-key map#
the same table consuming its other key
constraints:
zonal_history:
dims: [generator, zone]
expression: sum(p, by=gen_zone, over=snapshot, into=zone) <= zone_cap
Sum between the two keys of a map#
the same table read between its two key columns: no value column is read, so the domain asks only that the row is there
constraints:
zonal_membership:
dims: [snapshot]
expression: sum(units, by=gen_zone, over=generator, into=snapshot) <= budget
at through a two-key map#
its adjoint, reading the slot the row's own snapshot puts the generator in
constraints:
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)
Arithmetic and literals#
Signs, division and number literals#
division, both unary signs, a sign beside a sign, floats with and without an exponent, bracketing
constraints:
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
Greek parameter name#
a Greek-named parameter, which is given — so the convention wins and it prints as the word
Infinity literal#
the infinity literal, which is the one way infinity prints
Named expressions#
Plain expression in a constraint#
names the plain expression: its symbol prints here, its definition once below
Cased expression in a constraint#
names the cased expression: its symbol prints here, its block once below
Plain named expression#
a plain named expression: its symbol prints where it is used, its body once as a definition
Expression defined by cases#
a quantity defined by region: no two cases overlap, and otherwise is the rest
expressions:
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
Data-only expression#
a data-only entry, so a where may compare it
Named expression in a condition#
an expressions: entry on a side, read by the name the file gave it
constraints:
capped:
dims: [snapshot, generator]
where: "spend_cap > 0 OR NOT is_flexible"
expression: p <= p_max
Reported expression#
nothing in the math reads it, so its divisor may carry a variable
Dual of a constraint#
the row dual of a constraint, the one builtin only an entry the math never reads may call
Shifts#
Cyclic and acyclic shift#
roll (cyclic) and shift (acyclic) in one equation
constraints:
ramp:
dims: [snapshot, generator]
expression: p - shift(p, along=snapshot, offset=1, edge='wrap') <= shift(p, along=snapshot, offset=1) + p_max
Filled and forward shifts#
the two translations ramp leaves out: a fill, and forwards
constraints:
edges:
dims: [snapshot, generator]
expression: >-
shift(p, along=snapshot, offset=1, edge=0)
<= shift(p, along=snapshot, offset=-1, edge=0) + p_max
Cyclic forward shift#
the cyclic translation forwards, which is a fourth symbol again
constraints:
ahead:
dims: [snapshot, generator]
expression: p <= shift(p, along=snapshot, offset=-1, edge='wrap')
Composed shifts#
two steps of one policy are one step; a zero step is none at all
constraints:
composed:
dims: [snapshot, generator]
expression: shift(shift(p, along=snapshot, offset=1), along=snapshot, offset=1) <= shift(p_max, along=generator, offset=0)
Nested shifts that do not compose#
a named offset under a numbered one stays two steps, not their sum
constraints:
uncomposed:
dims: [snapshot, generator]
expression: shift(shift(p, along=snapshot, offset=lead, edge=0), along=snapshot, offset=1) <= p_max
Shift along two dimensions#
two dimensions translated at one leaf, each with its own policy
constraints:
crossed:
dims: [snapshot, generator]
expression: shift(shift(p, along=snapshot, offset=1, edge='wrap'), along=generator, offset=-1) <= p_max
Shift by a parameter offset#
an offset the data carries, so it prints as a symbol rather than a number
constraints:
lead_time:
dims: [snapshot, generator]
expression: shift(p, along=snapshot, offset=lead, edge=0) <= p_max
Cyclic shift within a group#
a translation partitioned by a relation: the group rides on the operator
constraints:
in_season:
dims: [snapshot, generator]
expression: p <= shift(p, along=snapshot, offset=1, edge='wrap', by=season_of, within=season)
Filled shift within a group#
the same group, with a fill: each season's opening row is kept and given a zero
constraints:
held_in_season:
dims: [snapshot, generator]
expression: p <= shift(p, along=snapshot, offset=1, edge=0, by=season_of, within=season)
Trailing windows#
Window of fixed width#
a trailing window of fixed width
constraints:
window:
dims: [snapshot, generator]
expression: sum_back(on, along=snapshot, window=3) <= units
Window with a parameter width#
the same window, its width in the data and its edge wrapped
constraints:
history:
dims: [snapshot, generator]
expression: sum_back(on, along=snapshot, window=min_up, edge='wrap') <= units
Window within a group#
a window partitioned by a relation: the group rides on the operator
constraints:
seasonal_window:
dims: [snapshot, generator]
expression: sum_back(on, along=snapshot, window=3, by=season_of, within=season) <= units
Piecewise 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.
Adjacency method#
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}"
Written out by spec.expand():
SOS2 method#
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:
Written out by spec.expand():
Convex method#
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:
Written out by spec.expand():
LP method#
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:
Written out by spec.expand():
Special ordered sets#
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.
Special ordered set of type 2#
at most two adjacent members nonzero, one set per snapshot
Written out by spec.expand():
Assumptions#
Two parameters compared#
two parameters, which is arithmetic like any other
Connective in an assumption#
a connective, so the line has no relation to align on
Parameter compared to a literal#
one parameter against a literal
Two relations compared#
two maps into one set, compared row by row
Reduction in an assumption#
a reduction on a side, leaving nothing to quantify
Shift in an assumption#
a translation inside arithmetic, and a position keeping the vacated row out
assumptions:
ramps_are_gentle:
holds: "load - shift(load, along=snapshot, offset=1, edge=0) <= budget"
where: "position(snapshot) > 0"
Assumption with a boolean condition#
a bare bool parameter as the where
Assumption with a relation condition#
a relation comparison as the where, over a frame two dims wide
Where conditions#
Parameter as a condition#
a parameter over nothing, and a mask that is a bare parameter
Variable and label conditions#
a mask on a variable's existence, and one on a dimension's label
constraints:
running:
dims: [snapshot, bus]
where: "theta AND snapshot >= 3"
expression: theta <= load
Position in a dimension#
a position in a dimension, and the same position within a group
constraints:
first:
dims: [snapshot, generator]
where: "position(snapshot) == 0 OR position(snapshot, by=season_of, within=season) == 0"
expression: on == 1
Position counted from the end#
the same two counted from the end, which print against a size rather than as themselves
constraints:
last:
dims: [snapshot, generator]
where: "position(snapshot) == -1 OR position(snapshot, by=season_of, within=season) == -1"
expression: on == 0
Relation compared to a label#
a relation compared to a label, to another relation, and to nothing
constraints:
northern:
dims: [snapshot, bus]
where: "zone_of == 'north' AND zone_of != area_of AND zone_of"
expression: slack <= load
Constant true condition#
a mask that is only the constant true, which the language says is no mask at all — so none prints
Constant true inside a condition#
the same constant inside a mask, where it is what the file says and prints
Constant false condition#
the other constant mask, which says the rows are none and is worth seeing
Comparison of two expressions#
a mask comparing two expressions, which prints as the arithmetic it is
constraints:
margin:
dims: [snapshot, generator]
where: "p_max - p_min > cost / 2"
expression: p <= p_max
Shift and pullback in a condition#
a translation under a comparison names its edge, a pullback reads through a relation, and the position keeps the vacated row out
constraints:
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
Reduction in a scalar condition#
a reduction on a side of a scalar mask, so nothing is left to quantify
constraints:
covered:
dims: []
where: "sum(p_max, over=generator) >= budget"
expression: sum(p) <= budget
Count over a dimension#
a count of the coordinates a predicate admits, which reduces one dim away
constraints:
counted:
dims: [bus]
where: "count(tech_cap > 0, over=technology) >= 2"
expression: theta <= budget
Count along a dimension of the frame#
the same count along a dim the frame carries, so the set takes a primed dummy
constraints:
counted_here:
dims: [bus, technology]
where: "count(tech_cap > 0, over=technology) >= 2"
expression: theta <= tech_cap
Predicate at the previous coordinate#
a predicate read one coordinate back, which is false where the translation vacates
constraints:
run_start:
dims: [snapshot, bus]
where: "load AND NOT shift(load, along=snapshot, offset=1)"
expression: slack <= load
Predicate through a relation#
a predicate read through a relation: a bus is held only where its zone has a cap at all
constraints:
zoned:
dims: [bus]
where: "at(zone_cap, by=zone_of, over=zone, into=bus)"
expression: theta <= budget