Skip to content

Parameters, variables, constraints and the objective#

These four blocks carry the math. Each takes an optional description:, free text that every tool reading the model keeps, and that the typeset legend prints.

parameters#

A parameter declares a shape and nothing more. The engine that builds the model supplies the numbers, by name, from its own tables.

dimensions:
  snapshot: { dtype: int }
parameters:
  load:
    dims: [snapshot]
  discount_rate:
    dims: [] # a scalar
Field
dims required. The dimensions it is indexed by. [] means a scalar
dtype float, int, bool, str default float
description free text default null

The dtype is a claim about the values, and the column has to match it:

declared the column
float a float column, or an integer one whole numbers are numbers, and this is the one widening allowed
int an integer column so a fractional position or offset cannot arrive
bool a boolean column 1 and 0 are not booleans. Cast the column, or declare int
str a string column

The dtype decides four things: whether the name is a value in an expression; what a where comparison is checked against; what a bare name in a where means; and whether the name may stand where an operator reads a position.

Only float and int are values. A str parameter is a label and a bool parameter is a mask: each selects rows in a where rather than scaling them, and writing either as a coefficient, a term or a divisor is a load error. A 0 or 1 that is meant to be multiplied by is declared dtype: int.

variables#

A variable is what the solver decides. There is one column per coordinate of dims.

dimensions:
  snapshot: { dtype: int }
  generator: { dtype: str }
parameters:
  capacity: { dims: [generator] }
variables:
  dispatch:
    dims: [snapshot, generator]
    where: "capacity > 0"
    bounds:
      lower: 0
      upper: capacity
Field
dims required. The dimensions it is indexed by
where which coordinates exist (absence) default null
bounds.lower / bounds.upper a number, or the name of a float or int parameter. Two numbers that cross are refused at load. A named bound is checked against its data default -inf / inf
domain continuous, integer or binary. binary carries fixed 0/1 bounds default continuous
absence undefined or zero: what a masked-out coordinate means (absence) default undefined
description free text default null

A bound you omit leaves the variable unbounded on that side

You write non-negativity. The language does not assume it.

A bound is a name or a number, never arithmetic: upper: capacity is accepted, and upper: -rating is refused. Ship the negated column as data. The dimensions of a bound parameter must not exceed its dims.

Equal bounds pin a variable (fix a quantity). A pinned variable is still a variable, so size * on is variable * variable, and a pinned variable cannot stand in another variable's bounds.

constraints#

One block is one rule. The name of the block is the name of the constraint, and that name is how a row is read back after a solve.

dimensions:
  snapshot: { dtype: int }
  generator: { dtype: str }
parameters:
  load: { dims: [snapshot] }
variables:
  dispatch: { dims: [snapshot, generator] }
constraints:
  power_balance:
    dims: [snapshot]
    expression: sum(dispatch, over=generator) == load
Field
dims required. The rows this rule builds
expression required. It uses exactly one of <=, >= or ==
where which rows are built (absence) default null
description free text default null

The dimensions of the expression must equal its dims. See how dimensions combine.

Either side of the comparator may carry variables, and one side must. A comparison between numbers and parameters alone is refused at load, because it is settled before the solve. A single row can still end up with no variable terms, because the data left its terms nowhere to sit. Such a row is not built. See absence.

dims: [] gives one scalar row, for a rule such as a system-wide budget. An empty dimension list means one value for a parameter, one column for a variable and one row for a constraint, so a scalar is never written as a dummy dimension of size 1. 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, each under its own where: (state a rule that differs by regime).

objective#

The objective is a single block with no name. Its value is a scalar, so there is nothing for a name to read back.

dimensions:
  generator: { dtype: str }
parameters:
  cost: { dims: [generator] }
variables:
  dispatch: { dims: [generator] }
objective:
  sense: minimize
  expression: sum(dispatch * cost)
Field
expression required. Arithmetic, with no comparator
sense minimize or maximize default minimize
description free text default null

The expression must be scalar. Anything else is a load error that names the sum it wants. Nothing is summed for you: sum(x * a) + sum(y * b) and sum(x * a + y * b) are both allowed, and they are different models.

There is one objective block. To pursue several goals, weight them into one expression.