feat(language): a lookup may map into its own dimension, so a representative snapshot is sayable - #436
feat(language): a lookup may map into its own dimension, so a representative snapshot is sayable#436FBumann wants to merge 2 commits into
Conversation
Documentation build overview
43 files changed ·
|
c4860be to
5adc494
Compare
…ntative snapshot is sayable
`rep_of: {over: snapshot, into: snapshot}` loads. The walked key is
consumed and the target produced, so the frame is unchanged through
sum(by=) and at(by=), and shift and position group within each
representative. The value column of every lookup's table is named after
the lookup rather than the target, which is the one rule a self-map needs
and the one every lookup now follows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FD5LpGRzAWdi5sKWXDdnHC
…ays why Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FD5LpGRzAWdi5sKWXDdnHC
5adc494 to
cb8ab96
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 with its base. This was stacked on #433, which lost #275 to #437 — merged as The capability is not lost. #424 is shipped, by role columns rather than by a lookup mapping into its own dimension: relations:
rep: { columns: { snapshot: snapshot, rep: snapshot }, key: snapshot }A relation may name one dimension twice, and the mapping form of One limit came with it and is recorded on #424: Generated by Claude Code |
Note
The following content was generated by AI. Stacked on #433, which is the base branch. Resolves #424. Draft, since it rides on a proposal the discussion under #275 has not yet taken; nothing in it depends on multi-key lookups, so it rebases onto main unchanged if #433 is not the base chosen.
What this changes
A lookup may map into the dimension it is keyed by:
The one refusal, "maps into itself", goes. No rule changes: the walked key is consumed and the target is produced, and here they are the same dimension, so the frame is unchanged through
sum(by=)andat(by=).shift(by=rep_of)walks inside each representative's group andposition(snapshot, by=rep_of)counts within it. The dim checks already pass as written, since they test the target against the operand minus the walked key, and the typesetter already primes the index: the golden case renders aswith
rep_of: 𝒯 → 𝒯in the legend.A self-map is always directional, and why
A lookup is a function: one value per key, and the declaration says which way the arrow points.
rep_ofsends every snapshot to its representative and never the other way. The two verbs are the two walks of that one arrow, exactly as for a cross-map:at(x, by=rep_of)sum(x, by=rep_of)shift(x, over=snapshot, by=rep_of),position(snapshot, by=rep_of)at(at(x, by=rep_of), by=rep_of)The inverse of a many-to-one map is one-to-many, so it is reachable as a grouping and never as a function. For a bijection, a successor map
next_of, the two walks are the two directions outright:atreads the successor's value,sumlands each value on its predecessor, one term per group.What has no direction is not a lookup. An undirected neighbour relation between buses is not a function of either end, so it is a parameter over
[bus, bus], where #433's placement table already routes every many-to-many relation, and a symmetric reading issum(w * x, over=...)with a weight table or aboolmask. The reference's "Into its own dimension" section carries this paragraph.Breaking, for every lookup's data. The value column of a lookup's table is named after the lookup, not the target:
gen_busarrives as columnsgenerator,gen_bus, where it arrived asgenerator,bus. This is the one decision the self-map forces, since a table keyed bysnapshotwith a value column also calledsnapshothas two columns of one name. Of the three ways out, this is the one rule with no special case, and it is what lpspec already does on read, which aliases the value column to the lookup name. The alternatives were a rule that names the column after the target except where the target is a key, and coroa's role-named columns (into: {rep: snapshot}), which is deferred with hisover: {bus0: bus}sugar as #433 says.One thing a self-map raises and this PR does not answer. Selecting the representatives themselves, the rows where the map is the identity, would be
where: "rep_of == snapshot", and that compares a lookup to a dimension, which the language refuses (a bare dimension name on the right is a string coordinate). The reference says to declare aboolparameter for them. Whether a lookup-to-dimension comparison should exist is a separate question, and only a self-map can ask it.Where it lands
model.py: the refusal removed from_lookup_targets; theLookupBlockdocstring names the case.intomay be a key; an "Into its own dimension" section with the example above, the frame rule, the directionality paragraph, and the identity-selection caveat; the data section names the value column after the lookup, with thesourcesexample updated.sum,at,shiftthroughrep_ofkeep the frame) and two predicate readings; the golden model gainsrep_ofand one constraint walking it both ways, in all three formats; notation regenerated.For lpspec
_read_relationreads the value column under the lookup's name rather than the target's: one argument._mappingneeds an alias on the key column, since both sides carrysnapshot.assign_coordsandselalready accept a coordinate over the same dim it indexes.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: 1235 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.Only the two generated files conflicted on the rebase onto #433's new head —
docs/reference/notation.mdandtests/typesetting/golden/markdown.out— and both were regenerated. Nothing in this PR's own two commits needed a hand resolution.Why
A representative-period map is the one capability the word "connector" in #6 reaches for that the language refused, and it needs no new construct: it is a lookup whose target happens to be its key. What it did need was a column-naming rule that does not collide, and the rule that has no special case is the one that names the value column after the lookup.
🤖 Generated with Claude Code
https://claude.ai/code/session_014FHQEdh9u2ppraSniF3iJd