Repository navigation
feat(data): a lookup is a relation, and one short of a key it declares total is refused at bind - #1575
feat(data): a lookup is a relation, and one short of a key it declares total is refused at bind#1575FBumann wants to merge 6 commits into
Conversation
…s total is refused at bind Follows the language to the head of energy-models/mathspec#443. A lookup is a table with one column per dimension it relates; `key:` says a row is identified by those columns and `coverage:` says whether every key tuple has one, which is the claim this bind now holds the data to. The relational engine walks the table as the call names it: a composite key, a landing on the product of two value columns, a bare relation summed through with both ends named, a self-map, and a produced dimension the operand already carries, which joins rather than lands. The linopy lane holds a lookup as an array over the dimension its key is over, so the three walks that are not a function of one dimension raise LaneError naming the engine that builds them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa
… that reads it named Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa
Merging this PR will not alter performance
Comparing Footnotes
|
…mes, tag or commit The ref was read with `grep -o 'math-spec@v[^"]*'`, so a pin at a commit — what a stacked language PR is pinned to before it has a tag — read as empty and `actions/checkout` quietly took the default branch, a corpus the pinned language does not accept. The step now takes any ref and fails when it finds none. The rungs and their tables are the parity runner's output, regenerated against the corpus at the pinned commit: a lookup's tidy table is named after the column the file declares rather than after the lookup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa
main landed #1576, the consumer side of math-spec#422, which this branch carries too by pinning the #443 stack — so every lookup declaration conflicted. The relational form supersedes the targeted one throughout: `attaching`, `loader` and `sources` take this branch's, `genx_piecewise_fuel` takes main's dimension names under this branch's `over:`/`key:` spelling, and the generated pages, the lockfile and the parity artefacts were regenerated rather than merged. `tests/test_label_coords.py` stays deleted, and the five claims main's version had that `tests/test_lookups.py` lacked are ported into it: the advice a walked dimension draws, #488's column over a dimension nothing spans, two lookups between one pair of dimensions, and a positional parameter's index order. The pin stays at the #443 stack head, which predates math-spec's alpha.85, so rung 10's projected description reverts to the corpus prose at that commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa
…ges keep the math they print The pin was the stack head as it stood, which forked before math-spec's alpha.85 — so regenerating every gallery page with that typesetter reverted #446's GitHub math delimiters across 62 files, and rung 10's description to the prose #442 had rewritten. Neither was this change's to make. The stack's own #437 branch had already been rebased onto main; only #443's one commit was left behind it. Rebased onto that head, and the pin follows it. The rebase brings #444 with it: a grouped position carries the walk it counts within rather than naming its pieces, so the predicate reads `partition` straight and rebuilds no walk of its own — and the linopy lane now holds a grouped position to the same one-dimension rule as every other walk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa
|
Note The following content was generated by AI. Closing: the language half of this is in Re-filed rather than annotated: this branch implements against Coverage that moved: the keyed-coverage commit here ( Generated by Claude Code |
Note
The following content was generated by AI. The consumer side of
energy-models/mathspec#443 and the #437 it is stacked on, pinned to
cb38f25— #443's commit rebased onto #437's current head.What this changes
A lookup is a relation — a table with one column per dimension it relates —
and the two claims a declaration makes about it are now held where the data is:
key:says a row is identified by those columns, andcoverage:says whetherevery key tuple has one, which is #443's feature and the reason a generator
lost in preparation is now named rather than dropping its terms in the join that
places them.
The relational engine walks the table as the call names it. One remap
serves both verbs, because the declaration fixes no direction and the
Walkcarries the one the call made: the dims it consumes, the dims it produces, and
the key columns it joins on all become join keys of one inner join. Five shapes
the engine could not build before now build, each with the answer worked out by
hand in
tests/test_lookups.py:sum(p, by=zone_of, from=generator, into=zone)from=periodsum(p, by=gen_bt, into=[bus, technology])sum(p, by=connection, from=generator, into=bus)p == at(p, by=rep_of)sum(load * p, by=gen_bus), a masked sumA partition keyed by two columns joins on the one it does not walk, so a
per-plant calendar ranks each plant's days apart.
Three walks part the lanes, and the linopy lane says so before linopy is
asked. It holds a lookup as a dense array over the dimension its key is over,
which is what
.groupby()and.sel()read — so a composite key, a barerelation and a self-map have no array, and each raises
LaneErrornaming whichand pointing at the engine that does build it. A bare relation a
wheretestsis refused the same way, and a grouped
position()is held to the same rule.docs/about/linopy.mdgains that as the lane's third wall, beside the objectiveconstant and the relational lane's own.
What moved in the corpus. Every model migrates once —
{over: generator, into: bus}becomes{over: [generator, bus], key: generator}— across 29 YAML files under
examples/andbench/, the 16 generateddifferential/rungs, and the test fixtures. Two declarations inexamples/and 22 in
tests/gainedcoverage: masked, because in each the gap is thesubject:
reserves' two open line ends, and the fixtures for a danglingat,a partitioned shift, a per-group width and a partial group.
The PyPSA parity gate read the corpus ref with
grep -o 'math-spec@v[^"]*',so a pin at a commit — what a stacked language PR is pinned to before it has a
tag — read as empty and
actions/checkoutquietly took math-spec's defaultbranch: a corpus the pinned language does not accept. The step now takes any ref
and fails when it finds none.
differential/pypsa/rungs/and its tables are therunner's own output and were regenerated; a lookup's tidy table is now named
after the column the file declares rather than after the lookup.
Why the pin is a rebase of #443 rather than its head
#443's commit sat on the #437 branch as it stood before math-spec's
v0.0.0-alpha.85, while #437 itself had since been rebased ontomain. Pinningthe stale head meant regenerating every gallery page with a typesetter that
predates math-spec#446, which reverted GitHub's math delimiters across 62
pages — 1276 lines of the diff — and reverted rung 10's description to the
prose math-spec#442 had rewritten. Neither was this change's to make.
So #443's one commit is rebased onto #437's current head as
cb38f25, and thepin follows it. The diff fell from 182 files (+5097/−5435) to 163
(+3012/−1838), and
docs/alone from +2755/−3980 to +672/−381.The rebase brings math-spec#444 with it: a grouped position carries the
Walkit counts within rather than naming its pieces, so
predicates.pyreadspartitionstraight and the walk it used to rebuild is deleted.cb38f25is pushed toenergy-models/math-speconclaude/mathspec-feature-lpspec-bcgaw5; #443's own branch still points at thepre-rebase
31a6595and wants a force-push tocb38f25to show it.Every guard deleted in turn
Re-taken on this head after the merge and the rebase, through
tools/mutate.pywhere it could be expressed as a deletion and by hand otherwise, with the same
three precautions —
git checkout --,__pycache__dropped on both sides, thetree asserted clean after. The baseline is 10 failed, 3595 passed: the ten
are
gurobiandxpresstests that do not skip when the solver package isabsent, and they fail identically on
origin/mainin this environment.coverage: totalguard (sources.py:288-289)sources.py:287)sources.py:284-285)sources.py:387-395)builder.py:149-151)builder.py:152-154)The last three are hand-taken: each is a changed expression rather than a
deletable statement, so
tools/mutate.pycannot express them.Verified
uvvenv on Python 3.12.3 with the repo's own pins; pixi is not installed here,so each gate was run as the task defines it.
pytest -q -n auto: 10 failed, 3595 passed, 328 skipped, 1 xfailed. Theten are the environment's, as above and unchanged from
origin/main.ruff check .,ruff format --check .,pyrefly check: clean.environment (
pypsa==1.3.0,highspy==1.15.1) against the corpus at thepinned commit: every rung matches PyPSA as deep as the engines allow, and a
second run confirms the gate's own
git diff --exit-code -- differential/pypsarewrites nothing.
ruff,pyreflyclean; all six generators re-run with no drift;mkdocs build --strictclean once the python.org inventory is dropped, whichthe proxy answers 403.
pytest bench/test_harness.py -q --i-know-another-is-running: 127 passed,9 skipped. The override is because the harness refuses a loaded machine and
no number here is a measurement — this is the harness's own correctness.
tools.gallery_mathandtools.ladderre-run and committed; the walkthroughgolden regenerated (
VariableDeclaration.variable_typeisdomainnow).parityincluded — the first run that resolves thecorpus at a commit ref through the fix above.
No benchmark was taken and none is claimed.
What this deliberately does not do
would mean a join in xarray, which is the thing this lane is a lane instead
of. Both the wall and the route around it are stated, tested and documented.
join, so nothing here claims a cost, and
bench/is untouched beyond themodel's own declaration. CodSpeed agrees: 16 untouched benchmarks.
tests/test_label_coords.pyis gone, andtests/test_lookups.pyreplacesit: its subject was the split between a targeted and a label-space lookup,
and math-spec#422 removed that kind. Everything still true moved over — the
flat namespace, the supplied-relation contract, the
wherecases, theordering and containment rules, A coordinate targeting an unused dimension is refused with a false reason: 'no coordinate set to check against' #488's column over a dimension nothing spans,
two lookups between one pair of dimensions, and the positional-parameter index
order — and the coverage and walk sections are new.
GroupSumno longer carries twotuples that could disagree, so the
zip(strict=True)that caught ahand-built mismatch is unreachable, and
test_a_hand_built_node_whose_tuples_disagree_is_refusedwent with it.tests/test_assertions.py's ratchet falls from 209 to 204.🤖 Generated with Claude Code
https://claude.ai/code/session_01SrEY2ZhFD1YXamRn4FzpMa