Skip to content

refactor(language): a relation is read in a direction rather than walked - #494

Merged
FBumann merged 6 commits into
mainfrom
claude/busy-volta-1z98iq
Sep 20, 2026
Merged

FBumann merged 6 commits into
mainfrom
claude/busy-volta-1z98iq

Conversation

@FBumann

@FBumann FBumann commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: "In computer science a walk is typically something like a reduce operation, ie you walk a tree or list and call a function on an accumulator and a node/entry one after the other, and i am always trying to map that to what is happening here, maybe by thinking of the ast being the tree that it is applied to, but it does not click for me."

Note

The following content was generated by AI.

What this changes

program.Walk becomes program.Direction, and the field GroupSum and At hold becomes direction. _Resolver._walk and _partition_walk become _direction and _partition_direction. Nine refusals say what a call does to a relation instead of describing a traversal, and the operators page, the dimension-set table, the declare-a-column how-to, the relation docstring the schema prints, three example descriptions and the tests say the same.

The axis sense is renamed too: shift, sum_back and position step along a dimension, in every refusal, docstring and page, and in the typesetter's position note, which is what moves the golden outputs and the generated example pages.

Why

A Direction is one relation and one join — which columns are consumed, which produced, which joined on. It is never a sequence and carries no accumulator, so "walk" promised a fold that is not there.

Since #533 the language says so itself: one call reads one relation, so there is not even a list to compose.

What keeps the word

The traversals that really are traversals: program.walk, walk_regions, _expression_parser.nodes, and the typesetter's own Walk class, which is a fold — _arithmetic combines each child's text into its parent's and _Context is the accumulator carried down. That module imported Walk as RelationWalk to hold both meanings at once; it imports Direction now and the alias is gone.

Stacked on this

#559 takes the partition out of Direction. A partition consumes and produces nothing, so Direction's docstring here still has to redefine its own fields for shift, sum_back and position. That paragraph goes with the split.

The refusals that changed
...names 2 relations, and one call reads one table
...this sum lands on the key ['g'], so each coordinate has one term
A call names both of its ends, so that a relation may gain a value column...
A relation is read between two of its columns and joined at the others...
A call brings the dims it lands on, so that reading it tells you what it adds
...and a partition steps along a key column over the dimension it groups
...steps along 'snapshot', but 'horizon' is declared over [...], which carries it
Why Direction, and not the alternatives

The tree already used the noun when it had to explain the concept: model.py ("An operator walks the table in the direction the call names … the declaration fixes no direction"), dimensions.py ("sum is the direction that produces them") and the relations page ("The declaration fixes no direction. A call names the columns it reads"). Renaming the class to it removes the second vocabulary rather than adding one.

The word is not unambiguous in prose. dimensions.py says a width "has no direction", meaning the sign of a shift along an axis, and the check and errors pages say "the direction a +slack term improves a minimize objective in". No sentence holds two of the three senses, and neither of the other two is a name in code.

Arrow was dropped on two collisions and one wrong implication: lpspec defines ArrowTable for the Apache Arrow PyCapsule stream in the module that reads relations as tables; typesetting/format.py already spells maps_to as \to / arrow.r; and an arrow reads as single-valued, which a direction over a bare relation is not.

Lookup describes at and not sum. Crossing collides with the cross join in lpspec's compiler, and Step with _Step in the typesetter. Read matches the verb the messages use, but the language already calls at "a read", so a Read that sum holds too would collide with the kinds table on the relations page.

The break

math_spec.program.Walk no longer exists; Direction is in program.__all__ in its place. lpspec reads the old name and needs a follow-up once this tags — it pins math-spec by tag, so nothing breaks before then.

Redone on today's main, not replayed

The branch was three commits on 4069792. Four merges since then — #504, #532, #533 and #535 — rewrote the same code and the same messages, so replaying the old diff would have conflicted on nearly every hunk and, worse, left the "walk" prose those PRs added untouched. A rename is a sweep, and the sweep has to cover the tree as it is now. The branch is reset to main and the sweep redone there, as one commit.

Two things the old branch planned no longer exist. GroupSum.walks and At.walks are walk singular since #533, so they become direction rather than directions. The docs commit that corrected a sentence about by= lists is moot: the list form went out with #533.

Merged with main, and what the docs rewrite moved

main was merged in at 0d93f01. Six files conflicted: program.py, resolution.py, operators.py and three language pages.

The source conflicts are all #540, which made within= required and took the partition's group from the columns it names. _partition_direction now takes within_roles: tuple[str, ...] rather than an optional one, and Direction's docstring says the group is the columns within= named.

The docs conflicts are the #543–#552 rewrite, which took the relations material out of dimensions.md into a page of its own. That page states the rule this PR argues for — "The declaration fixes no direction. A call names the columns it reads" — so the ### Walks → ### Directions rename this PR planned has nothing left to rename. What the sweep reaches instead is the prose those merges wrote: four sentences on the operators page, the error column of the dimension-set table in expressions.md, and two rows of docs/howto/declare-a-column.md.

The second sweep

A review of the first sweep found "walk" for a relation in the relation docstring the schema prints, in Direction's own docstring, in three node docstrings, in three example descriptions, and in the names, ids and messages of the tests. It also found the axis sense half renamed: four places said "steps along" and six still said "walks". Commit 6526f76 reaches all of them, and regenerates the schema, the goldens and the generated pages.

Rebased onto main at bda1fc5

Merged, not replayed: the hard rule here is never to force-push, and this
branch already carried two main merges. main brought #573, #576 and #529,
and was carried up all six branches stacked above this one.

One conflict, in dimensions.py. #529 dropped RelationNode.shown for
__str__ on the same line this branch changed "walks" to "reads". The
resolution takes both: f'sum(by={by}) consumes {missing}, the dims it reads from,'.

#529 also added test_a_node_resolution_built_prints_the_name_the_file_wrote,
which constructs program.Walk — renamed to Direction here. That file
auto-merged without a conflict and did not compile, so a clean merge was not
a passing build at any level of this stack.

Gates

pixi.sh is refused by this environment's egress proxy, so the gates ran from
a uv environment on Python 3.12, which is a departure from the "everything
runs in a pixi environment" default. Re-run on the rebased head; the counts
below replace the pre-rebase ones.

gate result
pytest -n auto 1330 passed, 6 skipped
ruff check, ruff format clean
pyrefly check 0 errors, 8 suppressed, at the pinned 1.2.0
typos, reuse lint clean
prettier clean on every changed page and example
mkdocs build --strict clean, with the docs.python.org inventory dropped for the run — the proxy refuses it with a 403
compile-tex not run locally, for want of a TeX distribution

The schema, the goldens and the generated pages were re-run and did not move.
The 6 skips are a missing typst, not failures. CI ran the whole gate on this
head, compile-tex included, and was green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5


Generated by Claude Code

@FBumann

FBumann commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@brynpickering Please approve this merge.
Its a refactor to improve the terminology used in the codebase

@FBumann FBumann added the area: relations relations and dimensions: the relation design label Sep 16, 2026
@FBumann
FBumann force-pushed the claude/busy-volta-1z98iq branch 2 times, most recently from 5d80d4c to 24af4e9 Compare September 16, 2026 17:29
@FBumann FBumann added the proposal: shared base Groundwork every proposal keeps, so it competes with none of them label Sep 17, 2026 — with Claude
`program.Walk` becomes `program.Direction`, and the field both nodes hold
becomes `direction`. `_Resolver._walk` and `_partition_walk` become
`_direction` and `_partition_direction`, and nine refusals say what a call
does to a relation instead of describing a traversal.

A `Direction` is one relation and one join — which columns are consumed,
which produced, which joined on. It is never a sequence and carries no
accumulator, so "walk" promised a fold that is not there. The language now
says so itself: since #533 one call reads one relation, so there is not even
a list to compose.

The word stays where the thing is a traversal: `program.walk`,
`walk_regions`, `_expression_parser.nodes`, and the typesetter's own `Walk`,
which is a fold with `_Context` as its accumulator. That module imported
`Walk as RelationWalk` to hold both meanings at once; it imports `Direction`
now and the alias is gone. The axis sense stays too — `shift`, `sum_back`
and `position` step along an ordered dimension.

The refusals that changed, in full:

    ...names 2 relations, and one call reads one table
    ...this sum lands on the key ['g'], so each coordinate has one term
    A call names both of its ends, so that a relation may gain a value column...
    A relation is read between two of its columns and joined at the others...
    A call brings the dims it lands on, so that reading it tells you what it adds
    ...and a partition steps along a key column over the dimension it groups

`dimensions.md` renames its `### Walks` section to `### Directions`, and the
two links into it follow.

This renames an exported name: `math_spec.program.Walk` no longer exists and
`Direction` is in `program.__all__` in its place. lpspec reads the old name
and needs a follow-up once this tags; it pins math-spec by tag, so nothing
breaks before then.

pytest: 1294 passed, 6 skipped. ruff check, ruff format, typos, prettier and
`mkdocs build --strict` clean — the strict build is what proves the renamed
anchor has no dead link left. pyrefly reports the 3 pre-existing missing
`yaml` stubs. The five generators were re-run: only `notation.md` moved, and
only where it quotes the golden model's comments. The golden `.out` files did
not move, so the typeset math is unchanged.

`pixi` cannot be installed in this environment, so the gates ran from a pip
environment on Python 3.13; `compile-tex` is CI's to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUhbXWGiyZF46wHp3fVnr2
@FBumann
FBumann force-pushed the claude/busy-volta-1z98iq branch from 24af4e9 to 6bcc68b Compare September 18, 2026 12:19
FBumann pushed a commit that referenced this pull request Sep 18, 2026
`dimensions.md` states the frame law and the four invariants the notation is
held to, above the bullets that were already its consequences. Two of the five
were on the page (the key is fixed, a call brings the dims it lands on) and
stay in their bullets, which carry the worked example each needs; three were
nowhere.

The law is the one the loader computes, checked against it line by line:
`_sum_dims` and `_at_dims` both return `(operand − consumed) ∪ produced`,
`_check_joined` holds the operand to `consumed ∪ joined`, and
`_check_lands_clear` refuses on `(produced ∩ operand) − consumed`. The joined
set is `key − (consumed ∪ produced)`, which is symmetric: for `sum` it reduces
to `key − consumed`, for `at` to `key − produced`.

The block says "a call" throughout rather than "a walk", so it reads the same
whichever way #494 goes.

Sentence length on the page: n 117, avg 16.8, median 17, 13 over 25 — against
n 105, avg 17.0, median 17, 13 over 25 before, so the block adds no long
sentence.

`mkdocs build --strict` clean, `tests/test_docs.py` 33 passed, prettier and
typos clean. The page is not generated, so no generator was involved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUhbXWGiyZF46wHp3fVnr2
The rename lands on the docs and the partition rules that #539–#552 and
#540 rewrote. The relations page main added already says "direction", so
the sweep now only reaches the four operator-page sentences, the
dimension-set table's error column and the declare-a-column how-to that
still said "walk". `_partition_direction` takes the `within=` columns
main made required, and `Direction`'s docstring says the group is the
ones `within=` named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mvLvAhLGB26wJ4gsCtRSe
…st that still walked a relation

The relation docstring that the schema prints, the Direction class's own
docstring, three node docstrings, three example descriptions and the test
names and ids all said "walk" for a relation. They say "read" now. The axis
sense was half renamed: four places said "steps along" and six still said
"walks". It is "steps along" everywhere, which reaches the typesetter's
position note and so the golden outputs and the generated pages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnbTmmYp7auaiCwSgkKKsB
The one conflict is the sum-to-key refusal, which #558 corrected on main
while this branch renamed it: the assertion keeps this branch's "lands on
the key" and #558's full at(..., by=lk, over=['h'], into=['g']) rewrite,
which the merged source emits. The rename sweep reaches the test #558
added, whose name and docstring called the read a walk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V2SFmoxEF3SnaPKp7HbZTk
@FBumann
FBumann removed this pull request from stack #560 September 19, 2026 18:10
@FBumann
FBumann added this pull request to stack #574 September 19, 2026 18:10
@FBumann
FBumann removed this pull request from stack #574 September 19, 2026 18:12
@FBumann
FBumann added this pull request to stack #575 September 19, 2026 18:12
Conflict in dimensions.py: #529 dropped RelationNode.shown in favour of
__str__, this branch renamed "walks" to "reads" on the same line. Both.

#529 also added a test that builds program.Walk, which this branch renamed
to Direction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
FBumann pushed a commit that referenced this pull request Sep 20, 2026
Carries origin/main up the stack of #494.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
FBumann pushed a commit that referenced this pull request Sep 20, 2026
Carries origin/main up the stack of #494.

#529 gave every expression node a __str__; this branch had split
RelationNode into DirectionNode and PartitionNode, so both get one.
The dims conflicts are #529's obsolete RelationNode arm against this
branch's Direction/Partition refactor, which wins.

The node-prints-itself test came from #529 against the two-field
RelationDeclaration and the unsplit node; it is rebuilt on both halves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
FBumann pushed a commit that referenced this pull request Sep 20, 2026
…plit-kdqegz

Carries origin/main up the stack of #494.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
FBumann pushed a commit that referenced this pull request Sep 20, 2026
…ession-parser-language-split-kdqegz-arithmetic-where

Carries origin/main up the stack of #494.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
FBumann pushed a commit that referenced this pull request Sep 20, 2026
… into claude/mathspec-piecewise-api-7j0wpt

Carries origin/main up the stack of #494.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
FBumann pushed a commit that referenced this pull request Sep 20, 2026
Carries origin/main up the stack of #494.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUyQztqnwapp3vHPhzBLw5
@FBumann
FBumann removed this pull request from stack #575 September 20, 2026 20:34
@FBumann
FBumann added this pull request to stack #583 September 20, 2026 20:35
@FBumann
FBumann merged commit bbc4344 into main Sep 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: relations relations and dimensions: the relation design

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants