one each
Every pupil is in one class.
Every generator sits on one bus.
math-spec · issue #275 · one of these three is merged, the other two close
This page is for whoever picks one of #428, #433 and #437. Five models are written three times, once per proposal. Every file is loaded on the branch that proposes it. Four of the five load under all three proposals, so there the choice is about what the file declares and what a call has to name. The fifth loads under one.
built 2026-09-11 09:32 UTC
base d64e5da
per: conditioning 4081e3e · keys and a dot 2d8735a · relations e7a95bc
The declaration keeps its arrow and names the dimensions the lookup varies along.
draft
The declaration takes a list of keys, and the call names the key it walks.
open, review requested
The declaration is a table of columns with a key, and the call names both ends of the walk.
open, review requested
What is measured, and what is not. Every YAML block was loaded on the branch it sits
under. Every list of dimensions under it is what that loader reported, and every equation is
what that branch's typesetter printed. comparison/verify.py makes the evidence
and comparison/build.py writes the page, so no snippet here is typed by hand. The
prose is not evidence. AI wrote it, and the trade-off at the end is one reading of the same
files.
0before the models the kinds of pairing
A lookup pairs the members of one dimension with the members of another: one generator with the bus it sits on. A lookup with no key is a relation, which claims nothing about how many and only says that a pair exists. Two questions decide how the file spells a pairing. How many does each thing have, and is there a number on the pair? Each row below answers both for one kind, in everyday terms first and then in a model, and under it every proposal writes the file it would write.
one each
Every pupil is in one class.
Every generator sits on one bus.
one each, and it changes
Every pupil is in one class, and the class changes each school year.
Every generator bids in one zone, and the zone changes by period.
two named slots
A journey has the station it leaves and the station it reaches.
A line has a bus at each end, and the flow leaves one and arrives at the other.
one of its own kind
Every pupil has one buddy, who is also a pupil.
Every snapshot has one representative snapshot standing in for it.
many each, nothing to count
A pupil can be in several clubs, and a club has several pupils.
A generator bids into several reserve products, and each product takes many.
many each, of its own kind
A pupil sits next to several others.
A region touches several other regions.
many each, with a number on the pair
Each pupil gets a number of biscuits at each club.
Each generator delivers to each bus at some efficiency.
Which kind you have
key. The loader then
holds the data to one row per key, and a second row is a refusal.Constraint 'reserve': 'eligible' is declared dtype: bool, and an expression is arithmetic — only dtype: float and dtype: int bind a column it can be done to. A flag masks rather than scales: name it in a where ("eligible", "NOT eligible"), which is what a mask is — or declare it dtype: int where the 0/1 is meant to arrive as data and be multiplied by.
The stand-in for an unweighted pairing is a table of ones, multiplied into the sum. The dtype that says “a flag” is refused in arithmetic, so the ones are declared as numbers instead.
Lookup 'rep_of' maps 'snapshot' into itself. A lookup maps into a different dimension.
Measured on #433's head. #436 is a draft stacked on it, and on that head the same file loads.
Parameter 'adjacent' names dimension 'region' twice. A frame is a product of distinct dimensions.
#436's description sends an undirected neighbour relation to “a parameter over
[bus, bus]”. That file does not load, on that branch or on any other. It is
the fifth model below.
1demands a lookup that varies along a second dimension
The generators bidding in a zone must meet its demand. Which zone a generator bids in changes by investment period, so the lookup is read at a period as well as at a generator. #161 opened this case, and all three proposals were written for it.
All three proposals print this. Only the name of the lookup changes, so the choice is about the file and not about the model it stands for.
2demands the same table, walked from its other key
This constraint reads the lookup of problem 1 the other way. Hold a generator and a zone, then sum its output over the periods it bid there. The table is the one the constraint above already uses. What differs is whether the file can say so with one declaration.
All three proposals print this. Only the name of the lookup changes, so the choice is about the file and not about the model it stands for.
3demands two columns over one dimension
Flow leaves one bus and arrives at another, so a line carries two bus labels. The balance at a bus sums generation there, plus flow arriving, minus flow leaving. The second constraint drops any line whose two ends are the same bus.
All three proposals print this. Only the name of the lookup changes, so the choice is about the file and not about the model it stands for.
4demands landing on two value columns at once
Each generator sits on a bus and has a technology. The cap applies to the pair, so the sum lands on both dimensions in one grouping. Each generator is counted once, at its own bus and its own technology.
All three proposals print this. Only the name of the lookup changes, so the choice is about the file and not about the model it stands for.
5demands a relation between two members of one dimension
A region is capped on what its neighbours produce. Neighbourhood carries no number. Two regions touch, or they do not, and each region touches several. Both columns of the lookup are regions, and that is what separates this case from every one above.
Only #437 prints this. The other two columns have no file that loads, so there is nothing to compare it against.
+outside the five
The language refuses this sum today, because bus would be needed twice. It
weights generation by the load at its own bus. Each term reads load at the
bus the generator sits on, and the sum lands on that same bus. Under relations the
produced column is joined on instead, so the sum is masked. This case is not one of the five above, and it is
the one capability difference they do not show.
=the whole comparison
Every proposal has its own row under every capability, even where two of them say the same thing. Each capability links to the model that measured it. The last block is the price, quoted from the pull requests, and nothing here checks it.
!what the loader says
This language fails at load, and its messages name the rewrite. A proposal that refuses a mistake well is worth as much as one that says more. These are the messages, unedited.
by=zone_of with no dotConstraint 'zone_balance': sum(by=zone_of): 'zone_of' is keyed by ['generator', 'period'], and the call has to say which key sum walks — the others are joined on and kept. Write by=zone_of.generator or by=zone_of.period.
Names both keys and both rewrites.
by=zone_of with no consume=Constraint 'zone_balance': sum(by=zone_of): 'zone_of' has 2 key columns (['generator', 'period']), and the call has to say which consume= names.
Names the key columns, and the keyword that has to pick one.
Constraint 'history': the expression carries dims ['period'] that are not in foreach ['generator', 'zone'] — every stray dim multiplies the rows this constraint builds; add it to foreach if that is intended, or sum it out.
The refusal is real, and it is an error about the frame, which is the set of dimensions a row carries. It is not an error about the lookup. So it cannot name the rewrite, which is a second declaration in another block.
?what is left to decide
Capability alone does not decide this. Four of the five models are sayable under all three proposals, because the two narrower ones reach the same rows with more declarations. What differs is where the file puts a fact, and what a reader of a call has to know already. The fifth model is the exception, and the one place a column is empty.
The call site is untouched. by=zone_of keeps the meaning it has today, and
the lookup keeps its arrow. The direction belongs to the declaration, so a table read
both ways is declared twice. The loader does not know the two are one table.
Is a call that names nothing worth a declaration per direction?
One declaration per table. The key list is the cardinality claim, and the dot names the key a call walks. A column is named after its dimension, so a table cannot hold two columns over one dimension. A line's two ends stay two lookups.
Is one new token at the call site the whole price worth paying?
A lookup is a table of named columns, and key: is the one claim the data
can be held to. Every direction belongs to the call, which names it with
consume= and produce=. This says everything the other two say
and more. It costs ten rules, three keywords, and a rewrite of every declaration in the
tree.
Does the language want a relation, or a map?