Conversation
Documentation build overview
43 files changed ·
|
9708c11 to
0481915
Compare
…e key a call names `over:` takes a list, and `by=zone_of.generator` says which key the operator consumes; the other keys are joined on and passed through. One table, several walks: `sum(p, by=zone_of.generator)` lands on `[zone, period]`, `sum(p, by=zone_of.period)` on `[generator, zone]`. The dot is required exactly where the lookup has several keys, and a walked key must be the walked dimension of shift, sum_back and position. A `where` reads every key. The map binds single-valued per key tuple. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FD5LpGRzAWdi5sKWXDdnHC
…ment table says so Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FD5LpGRzAWdi5sKWXDdnHC
0481915 to
2d8735a
Compare
…e only #437 can say Adds a primer above the models: seven cards, each one kind of pairing in everyday terms first and then in a model, with the spelling and what each proposal does with it. Every `says` entry is measured — the file is under `models/` or `probes/` and the branch's own answer is in `evidence.json`. Adds a fifth model, `models/p5`: which regions are neighbours. Both columns are regions, so the fallback for a relation — a parameter over the pair — is refused, and the two left-hand panels print that refusal instead of a frame. `build.py` renders a refused model as evidence rather than treating it as a failure. Two measured findings the primer prints verbatim: - a `dtype: bool` flag cannot be multiplied, so the table of ones that stands in for an unweighted relation has to be declared `dtype: int`; - #436's description sends an undirected neighbour relation to "a parameter over `[bus, bus]`", and that file does not load, on #436's own branch or any other. `verify.py` gains #436's branch, which is not a fourth proposal but the draft stacked on #433: a claim about the self-map is a claim about #433 with #436. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4kdrj2n7tESNrgYmfXkAq
Note The following content was generated by AI. Closed: #275 is decided, and #437 merged as This PR's design is the one #437 was built out of, and its own body says so: "#433 answered coroa's design by keeping the lookup a function and letting the call pick which key it walks. The objection was that a table has no direction until something walks it, and the direction belongs to the verb." #437 takes that one step further — the declaration keeps only the claim the data can be held to ( What carried over: keys as the cardinality claim, walking a table from either key column, and the bind check per key tuple. What did not: the dot at the call site, which became Capability rows where the two differed, from #437's comparison: several value columns walked to one, landing on a product of two value columns, reading a two-column slot at once, removing two key columns at once, a line's two ends in one table, a calendar table partitioning at three levels, and a masked sum where the produced dimension is already carried. #436 built the self-map on top of this branch and is closed with it; #424 is answered by roles instead. Generated by Claude Code |
Note
The following content was generated by AI. This is the proposal for #275's remaining question and #161, built on the design @coroa sketched in this comment. It is an alternative to #428 (
per:), which it supersedes if adopted; both are open so the two can be compared on the same base.The proposal, on one complete model
Generators bid in zones, and a generator's zone changes by period, so the map has two keys. This model loads on the branch as written, and the dimensions in the comments are what the loader reports.
Every walk of
zone_ofnames the key it walks after a dot. The other key is joined on and kept.The data for
zone_ofis one table with columnsgenerator,period,zone, one row per(generator, period). That one table serveszone_balance,history,capped_revenueandfirst_in_zone. Under #428historyneeds a second declaration of the same table, sinceper:fixes which key is consumed.One block, one kind: a lookup is a single-valued map from its key dimensions into a target.
over:is one dimension or a list. A call walks one key, named after a dot, and joins on the rest.What each walk does to the dimensions
sum(p, by=zone_of.generator)generator, periodgeneratorperiodzonezone, periodsum(p, by=zone_of.period)generator, periodperiodgeneratorzonegenerator, zoneat(price, by=zone_of.generator)zone, periodzoneperiodgeneratorgenerator, periodat(x, by=zone_of.period)zone, generatorzonegeneratorperiodgenerator, periodshift(p, over=period, offset=1, by=zone_of.period)generator, periodgeneratorgenerator, period; the step stays inside each(generator, zone)groupposition(period, by=zone_of.period) == 0in awheregenerator, periodgeneratorzone_of == 'A'in awheregenerator, periodAsumconsumes the walked key and produces the target;atconsumes the target and produces the walked key;shift,sum_backandpositionwalk the key they already name and group within the other keys. In every row the keys not walked stay in the result. A one-key lookup is the same table with an empty "joined" column, andby=gen_busisby=gen_bus.generator.The rules, each decided at load
over:is one declared dimension or a list of them, each named once;into:is a declared dimension that is not a keyby=name.keynames the key the operator walks. Required where the lookup has several keys; with one,by=gen_busandby=gen_bus.generatorare the same callsum, produced byat; every other key is joined on: the operand carries it and the result keeps itshift,sum_backandpositionwalk the dimension they already name, so the dotted key must be that dimensionby=[a.k, b.k]list is one grouping, so every entry walks the same dimension; each joins on its own other keys, and no two target the same dimensionwherenaming a lookup — bare, compared, or compared to another — reads every key, so the frame carries all of them, and two compared lookups have the same keysDataErrorRule 7 is the one #161 asked for: a generator in two zones in one period is a refusal, where a
0/1membership parameter said it legally and silently.Decisions taken, and why
The dot instead of
per:(#428).per:fixes the consumed key in the declaration, sosum(p, by=zone_of.period)needed a second declaration of the same table. The dot moves that choice to the call, which is where the direction of a walk is already chosen (sumvsat). Nothingper:said is lost: aperlist of any length is the same as a longerover:list with one key walked. The consumer nodes are the same shape either way, withkeysin place ofper.coroa's function kind is taken whole; a bare relation (no
into:) is a parameter, and the placement table now says so. Asked use by use what a relation withoutinto:says that the language cannot: selecting on it is aboolparameter; the weighted aggregation in coroa's own example,sum(efficiency * p, by=connection.bus), issum(efficiency * p, over=entity)today, becauseefficiency[entity, bus]has a row exactly where the pair exists; an unweighted fan-out issum(p * connection, over=entity)with a0/1parameter; andat,shift(by=)andposition(by=)are undefinable through a many-to-many map, since a row has several values or several groups. The kind makes no cardinality claim, so it checks nothing a parameter does not. What it would add is the legend printingconnectionas structure rather than data — the purity argument that kept the label-space kind alive until #422. The rules also do not close: with no declared side, the dot would name the produced column and the rest would be consumed, the opposite of its meaning on a function. The real many-to-many shapes are not relations on inspection: a PyPSA multi-link is{over: [link, port], into: bus}, a function of two keys; a cycle incidence is signed weights; a technology present at some nodes is a mask. The "Dimension, lookup or parameter?" table gains the row relates members of two dimensions many-to-many → a parameter over both, with the rewrite beside it.Columns are named after their dimensions, not roles. coroa's
over: {bus0: bus, bus1: bus}sugar is the natural spelling for a self-map (#424) and for two columns over one dimension. Nothing in this PR can walk such a lookup: a walk needs one axis per dimension in the operand, so a secondbuscolumn has nothing to join on. Deferred with #424, where the self-map is what needs it (#436 takes it up).lookupsstays the block name.groupsreads wrong forat(by=)and for awherefilter;mapsis the better noun but the rename buys a reader nothing the legend does not already show. A one-day change if the discussion prefers another word.The dot is not admitted in
wherecomparisons. A comparison reads the whole key table, so there is no key to choose;position(d, by=l.k)is the onewhereform that walks, and therekmust bed(rule 4).What consumers see
LookupDeclaration(name, target, keys), withkeysin declared order.GroupSumandAtgainkeys: tuple[tuple[str, ...], ...], one per coordinate, withoverstill the walked dimension.LookupComparisonNode,LookupPairComparisonNodeandLookupDefinedNodecarrykeysin place ofover;DimensionPositionNodecarries the lookup'skeysbesideby.Program.lookupsis a dict by name (a two-key lookup sits under bothDimensionDeclarations, once in the dict). The legend printszone_of: 𝒢 × 𝒫 → 𝒵, and every applicationzone_of(g, p).The golden model's new lines, rendered
$$p_{t,g} \le \mathit{spill}{t} \cdot \mathrm{zone_cap}{\mathrm{gen_zone}(g, t)} \qquad \forall t, g : \mathrm{gen_zone}(g, t) = \text{'north'} \wedge \mathrm{pos}_{\mathrm{gen_zone}(g, t)}(g) = 0$$
Where it lands
_expression_parser.py,_where_parser.py: a dotted name is a token in aby=value and nowhere else.model.py:over: str | list[str]with akeysproperty; rule 1 in_lookup_targets.resolution.py:_walked(rules 2, 4 forposition), the list rule (5), the pair rule (6);Namespace.lookupsisname -> (keys, into).dimensions.py:_check_joined(rule 3), the walked-key check for translations (rule 4).program.py,lowering.py:keyson every node above;Program.lookupsby name.typesetting/walk.py: a map prints its keys in declared order, the walked one as the reduction's dummy.lookupsfield table, a "Keyed by several dimensions" section carrying the rules above, the many-to-many row and rewrite in the placement table, rows in operators and expressions; notation regenerated.Verified
On the current head, in a Python 3.13 venv built from PyPI (pixi.sh is blocked in this environment, so the tool versions are the latest rather than the pinned ones):
pytest -q: 1231 passed, 6 skipped.ruff check .: all checks passed.ruff format --check .: 127 files already formatted.tools.schema,tools.notation,tools.spec_math,tools.home_math,tools.gallery,tests.typesetting.golden— andtests/test_docs.pypasses on the result.Not run:
pyrefly,reuse lint,typos,prettier --list-different,compile-tex, andpixi run cias a whole.mkdocs build --strictreaches its last step and then aborts on the one error the proxy causes — it cannot fetchhttps://docs.python.org/3/objects.invfor mkdocstrings.Four conflicts against main were resolved by hand:
resolution.py: main lifted the kwarg-name reading into a sharednames_in()(chore: the separability walk leaves program.py, and every table keyed by an operator is pinned to the closed set #418) where this branch inlined the same twoisinstancearms;_lookup_refnow callsnames_inand keeps this branch'swrittenspellings, which carry the dotted key.typesetting/walk.py: main names the where-precedence levelcomparison(feat(program): a where predicate's operands are walked with where_children, as an expression's are with children #416/chore: the separability walk leaves program.py, and every table keyed by an operator is pinned to the closed set #418) where this branch still wrote the literal2; the three lookup predicates keep main's name and this branch'snode.keysarguments.tests/test_lowering.py:test_a_binary_variable_lowers_to_a_vtypeistest_a_binary_variable_lowers_to_a_binary_domainon main (refactor(program): a variable's domain is called domain in the program, as the file calls it #427); the new two-key lowering test sits beside it under main's name.docs/reference/notation.mdandtests/typesetting/golden/markdown.outare generated, so they were regenerated.The model at the top loads through
to_specandto_programon the branch, and the frames in its comments are the ones the program reports — unchanged by the rebase, and re-checked by the suite above.Why
Bryn's complaint in #7 was that
lookups:mixed what could act as a dimension with what could not; #422 removes the second kind. coroa's comment shows the remaining gap is not a second block but a second key: the same map varies along more than one dimension and can be walked along either. The dot says which, once, at the call, and every rule above follows from that one sentence.🤖 Generated with Claude Code
https://claude.ai/code/session_014FHQEdh9u2ppraSniF3iJd