Parameters, variables, constraints¶
The four blocks that carry the math. Each block takes an optional
description: — free text, never parsed, no length limit. Unlike a # comment
it is part of the loaded model, so it reaches everything downstream: the
typeset legend prints the one on a dimension, parameter or
variable.
A description is plain prose, in no notation. Every output format sets the
same words as text, escaping whatever its own syntax would otherwise read as
markup — an underscore stays an underscore, and a $\ell$ prints as those five
characters rather than as a symbol. Write the thing rather than its symbol —
"flow on a line", not "flow on line \(\ell\)".
parameters¶
Declared shape only; the numbers bind by name at run time (data binding).
dimensions:
snapshot: {dtype: int}
parameters:
load:
dims: [snapshot]
discount_rate:
dims: [] # a scalar
| Field | ||
|---|---|---|
dims |
required — the dimensions it is indexed by; [] is a scalar |
|
dtype |
float, int, bool, str |
default float |
description |
free text | default null |
dtype is a claim about the values, and the column has to be it. It
decides three things — what a where comparison is checked against, what a
bare where on the name means
(where strings), and whether the name may stand
where an operator reads a
position — so a column that
disagrees describes a model the data does not build, and does not bind
(data binding).
| declared | the column | |
|---|---|---|
float |
a float column — or an integer one | whole numbers are numbers, the one widening |
int |
an integer column | which is why a fractional position cannot arrive |
bool |
a boolean column | 1/0 is not one; cast it, or declare int |
str |
a string column |
variables¶
What the solver decides — one column per coordinate of foreach.
dimensions:
snapshot: {dtype: int}
generator: {dtype: str}
parameters:
p_max: {dims: [generator]}
variables:
p:
foreach: [snapshot, generator]
where: "p_max > 0"
bounds:
lower: 0
upper: p_max
| Field | ||
|---|---|---|
foreach |
required — the dim signature | |
where |
which coordinates exist (absence) | default null |
bounds.lower / bounds.upper |
a number, or the name of a parameter | default -inf / inf |
domain |
continuous, integer or binary — which carries fixed 0/1 bounds |
default continuous |
absence |
undefined or zero — what the masked-out coordinates mean (absence) |
default undefined |
description |
free text | default null |
Omitting a bound means unbounded on that side — non-negativity is written, not assumed.
Bounds take a name or a number, never arithmetic. upper: p_max is fine;
upper: -rating is not, and the error says so rather than reporting a parse
failure. Ship the negated column as data. (Expressions there are
#31.) A bound parameter's dims
must not exceed foreach.
Equal bounds pin a variable, which is how one declaration covers a quantity
that is a decision in one model and data in another: bind lower and upper
to the same value where it is fixed, and rate - relmax * size <= 0 is one
equation whether size is chosen or given. Presolve substitutes the pinned
column, so the solver receives the LP the pre-multiplied form would have
produced. Two limits: a pinned variable is still a variable, so size * on is
refused as variable × variable (expressions), and it cannot
appear in another variable's bounds.
constraints¶
One rule per block. The block's name is the constraint's name, which is what a row is read back by after a solve.
dimensions:
snapshot: {dtype: int}
generator: {dtype: str}
parameters:
load: {dims: [snapshot]}
variables:
p: {foreach: [snapshot, generator]}
constraints:
power_balance:
foreach: [snapshot]
expression: sum(p, over=generator) == load
| Field | ||
|---|---|---|
foreach |
required — the rows this rule builds | |
expression |
required — exactly one of <=, >=, == |
|
where |
which rows are built (absence) | default null |
description |
free text | default null |
The expression's dims must equal foreach
(dim algebra). Either side may carry the
variables; a row that ends up with none on either side is not a constraint and
is not built (absence).
foreach: [] is one scalar row — a single system-wide budget, where the
expression reduces every dim away. Nothing special: sum(x, over=f) <= 120 has
no free dims, so [] is the signature that matches it. An empty dim list is
the empty coordinate everywhere it appears — one value for a parameter's
dims: [], one column for a variable's foreach: [], one row for a
constraint's — so a dummy dimension of size 1 is never how a scalar is written.
One gap: a scalar variable may not carry a where
(#340); put the condition on
the constraints that use it.
Two regimes of one rule are two blocks, and each gets a name a reader chose rather than a position in a list:
storage_balance:
foreach: [snapshot, storage]
expression: soc == shift(soc, over=snapshot, offset=1) * (1 - loss) + charge - discharge
storage_balance_initial:
foreach: [snapshot, storage]
where: "snapshot == index(snapshot, 0)"
expression: soc == soc_initial
shift vacates the first snapshot and a vacated position is
absent, so that row drops without a where saying so. Spelling
it edge='wrap' gated on where: "snapshot > 0" builds the same rows here and
a different model on a horizon that does not start at 0 — the gate hardcodes
the origin, the operator does not.
objective¶
A single block, not a mapping, and it carries no name — there is nothing a name would read back, the value being scalar.
dimensions:
generator: {dtype: str}
parameters:
cost: {dims: [generator]}
variables:
p: {foreach: [generator]}
objective:
sense: minimize
expression: p * cost
| Field | ||
|---|---|---|
expression |
required — arithmetic, no comparator | |
sense |
minimize or maximize |
default minimize |
description |
free text | default null |
There is no foreach: an objective is scalar by definition, and every dim
the expression carries is summed. Each term is summed over the dims that
term carries, and is not repeated because another term carries a dim it does
not: in x * a + y * b with x, a on i and y, b on j there are
|i| + |j| summands, never |i| · |j|.
A second objective is unsayable rather than checked — the schema holds one block. Weight several goals into one expression.