Skip to content

The language#

A model is one YAML file. It declares the axes the model runs over, the data it expects, the decisions the solver makes, and the rules those decisions obey — and nothing else: no Python state changes what a file means, and the same file means the same model whichever solver takes it.

dispatch.yaml
description: Least-cost dispatch of a generator fleet against an hourly load.

dimensions:
  snapshot: { dtype: int }
  generator: { dtype: str }

parameters:
  load: { dims: [snapshot] }
  cost: { dims: [generator] }
  p_max: { dims: [generator] }

variables:
  p:
    foreach: [snapshot, generator]
    where: "p_max > 0"
    bounds: { lower: 0, upper: p_max }

constraints:
  power_balance:
    foreach: [snapshot]
    expression: sum(p, over=generator) == load

objective:
  sense: minimize
  expression: sum(p * cost) # an objective is one number, so the sum is written

That file is a complete model; the pages here are the exact rules.

Ten rules the language reduces to#

Nothing is guessed. Where a file does not determine the answer, loading fails and the message names the rewrite. Every rule below is that one principle in a different position, and each links to the page that spells it out.

# Rule
1 Ten declaration keys plus version and description, and the schema is closed at every level — an unknown key is an error naming the near miss. Booleans are YAML 1.2, so no / on / off stay names. File shape
2 Everything decidable without data is decided without data. Errors
3 One flat namespace, no shadowing — a collision is a load error naming both declarations. Names
4 Position decides which kinds of name are legal, and a name's kind is fixed at load time. A dimension is never legal in a value position: it is a coordinate space, not data. Names
5 Dim sets compose by union. A constraint must equal its foreach; an objective must carry none; a where or a bound must not exceed its frame. Dim algebra
6 Four constructs create absence, and nothing else does. It is a state of a variable; a constraint's own where: deletes its row directly. Absence
7 Through arithmetic absence spreads, taking the row with it. Out of a reduction it does not — so sum(x + y) and sum(x) + sum(y) are different questions. Absence
8 Identity of the position. A missing value reads as whatever makes it contribute nothing — zero as a coefficient, false in a where. Where no such reading exists it is refused: a divisor, a bound. Absence, Operators
9 Degree 2 in the math, degree 1 beside it: the objective and constraints take variable * variable; a bound, a named expression and a piecewise: link do not. / always needs a variable-free divisor, and ** a base and an exponent that carry no variable. Where a quadratic model can land is a consumer's axis, not the language's. Expressions
10 The operator set is closed. Compositions go in macros:. Operators

The pages#

File shape the ten keys, version, description, and how the YAML is read
Dimensions and lookups the axes, and the maps their members carry
Parameters, variables, constraints the four blocks that make up the math
Expressions the two grammars — arithmetic and where — what a name may mean where, and how dims compose
Reported expressions the entries the math never reads — read off a solve, where the math-only restrictions lift
Operators sum, at, shift — the closed set
Absence and where what a mask means: which rows are built, and which are not
Piecewise curves and SOS piecewise: and sos:
Errors and limits what fails when, and what the language will not say

Running a model — building, solving, reading an answer back — belongs to a consumer of the AST, not to this package. Nothing a consumer does changes what a file means.