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: {values: [wind, solar, gas]}
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: p * cost # an objective sums every dim it carries
That file is a complete model. Write a model walks through the ideas behind it; 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 labels. |
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; 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 | A missing value reads as the identity of its position — zero as a coefficient, false in a where. Where the position has no identity it is refused, rather than guessed: a divisor, a bound. |
Absence, Operators |
| 9 | Degree 1, always: * needs a variable-free factor, / a variable-free divisor, ** is refused. Bounds are narrower still — a name or a number, never arithmetic. |
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 |
| 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: |
| Data binding | what sources accepts, and how coordinates are resolved |
| Errors and limits | what fails when, and what the language will not say |
Running a model — building, solving, reading an answer back — is the Python API. Nothing there changes what a file means.