Skip to content

refactor(program): a program names its nodes by the naming rule and its groups as the file does - #475

Closed
FBumann wants to merge 4 commits into
mainfrom
claude/focused-curie-t1d2hz
Closed

FBumann wants to merge 4 commits into
mainfrom
claude/focused-curie-t1d2hz

Conversation

@FBumann

@FBumann FBumann commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: review the Program architecture of lpspec. Are the naming of methods and classes clear? Are there better alternatives? Can we improve the architecture?

Follow-up: Yes, turn all of this into a PR

Note

The following content was generated by AI.

What this changes

Every public name in math_spec.program now follows the naming rule in docs/contributing.md, and a relation and a named expression are read from the program the way the file declares them. Nothing the language accepts, refuses or prints changes. Regenerated on top of #437 and #477.

This is a break, described here because the alpha stream refuses the marker. Every rename, one row per old name:

Where nodes

Was Is
AndNode And
OrNode Or
NotNode Not
BooleanLiteralNode BooleanLiteral
ParameterDefinedNode ParameterDefined
VariableDefinedNode VariableDefined
ParameterComparisonNode ParameterComparison
DimensionComparisonNode DimensionComparison
DimensionPositionNode DimensionPosition
RelationComparisonNode RelationComparison
RelationPairComparisonNode RelationPairComparison
RelationDefinedNode RelationDefined

Unions

Was Is
ExpressionNode Expression
WhereNode Predicate
TypedPredicateNode TypedPredicate
ConnectiveWhereNode Connective

Expression nodes and their fields

Was Is
At Pullback
Window WindowSum
Translate.dimension Translate.along, the keyword shift takes since #477
Window.dimension WindowSum.along, the keyword sum_back takes since #477

Program and declarations

Was Is
Program.named_expressions Program.expressions, the file's own expressions: section
Program.expressions (the objective and both sides of every constraint) Program.roots
Program.relations, a property built from the per-dimension nesting Program.relations, a field: a Mapping[str, RelationDeclaration] keyed by name like every other group
DimensionDeclaration.relations gone; DimensionDeclaration carries dtype alone
RelationDeclaration(name, columns, key), a NamedTuple RelationDeclaration(columns, key), a frozen dataclass; the name is its key in Program.relations
Walk(relation, consumed, produced, joined), a NamedTuple whose name read off the relation Walk(name, relation, consumed, produced, joined), a frozen dataclass
Footprint.shapes Footprint.nodes

Deleted

Was Now
Expression base class, with __add__ and __mul__ the nodes share no base; Expression is the union. Build Add(a, b) and Multiply(a, b)
Program.dimension(name) program.dimensions[name]
Program.parameter(name) program.parameters[name]
Program.variable(name) program.variables[name]

Kept, deliberately: Sum.over, and over, into and coordinate on GroupSum and Pullback. #477 settled the call keywords on over= and into=, and the program follows the file. Also kept: the public walk helpers with no caller in this tree (fan_in, is_quadratic, parameters_of, quotients, divisor_parameters, check_message, Mask.conjuncts, Mask.names_read), since whether lpspec reads them is not visible from here.

Why

The naming table says Node marks the core AST and the program uses bare names, yet the program's where vocabulary carried the suffix. At was the file's spelling where the rule asks for the coordinate map, and Window did not say it sums. A translation and a window called their axis dimension where every other node and the file itself name it. The file's expressions: arrived under a different name while Program.expressions meant something else. Relations were nested under each dimension they touch, one relation under several, and a property undid the nesting; with the group keyed by name, the declaration's own name field was a second home for one fact. The singular accessors covered three of five groups, and one had no caller. The operator sugar contradicted the reading page's "you never build a node yourself".

Verification

pixi is not installable in this session (the installer host is blocked), so the gates ran from a uv venv on Python 3.12 with the same pinned ruff==0.16.1 and pyrefly==1.2.0.

Ran on the current head:

  • ruff check and ruff format --check on src, tests, tools: clean.
  • pyrefly check --python-interpreter-path <venv>: 0 errors, 9 suppressed, same as main.
  • pytest -q -n auto: 1265 passed, 6 skipped. The 6 skips are the typst compile tests, which skip without the typst binding.
  • python -m tools.schema, python -m tests.typesetting.golden and the four page generators: no diff.
  • mkdocs build --strict: builds, with the docs.python.org inventory line removed locally because that host is blocked here. The config is unchanged in the commit.
  • prettier --check on every Markdown file: clean.

Not run: reuse lint, typos, taplo, zizmor, compile-tex, and pixi run ci as one command.

Coverage moved:

  • test_an_unknown_dimension_is_a_near_miss_rather_than_an_empty_declaration is deleted with the accessor it tested.
  • test_a_relation_names_the_dimension_its_values_label is replaced by test_a_relation_is_declared_as_the_file_declares_it, which lowers a file rather than hand-building a program.
  • test_expressions_are_the_ones_a_row_is_built_from is test_roots_are_the_trees_a_row_is_built_from.
  • test_a_program_seals_its_declaration_groups now covers relations too.
  • Two tests that built nodes with * and + now call Multiply and Add. The review claimed the sugar had no caller in tests; it had these two.
  • The golden test's carrier set gains Walk and RelationDeclaration, which its dataclass walk now reaches.

Departed defaults: the branch name is the one this session was given rather than <type>/<topic> in a worktree; the renames and the relation restructure ship in one PR at the follow-up's ask rather than stacked; and main was merged in rather than the branch rebased, because the project forbids a force-push.

🤖 Generated with Claude Code

https://claude.ai/code/session_019fhGZgaBspo7mh9Hjd3KtT

…ts groups as the file does

The where vocabulary drops the parser's `Node` suffix, so `And`, `Not`,
`Or` and `ParameterComparison` stand beside `Sum` and `Add` as the naming
rule says; the unions are `Expression`, `Predicate`, `TypedPredicate` and
`Connective`. `At` is `Pullback`, named for the coordinate map rather than
the file's spelling, and `Window` is `WindowSum`, paired with `GroupSum`.
A translation and a window call their axis `over`, as every other node
does.

The file's `expressions:` section is `Program.expressions`, and the trees a
row is built from are `Program.roots`. Lookups are one group keyed by
name, each with `over` and `into`, as the file declares them, rather than
nested under a dimension with `target`. `Footprint.shapes` is `nodes`.

Gone: the base class and its `+` and `*` sugar, the singular accessors
`dimension()`, `parameter()` and `variable()`, and
`DimensionDeclaration.targets`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fhGZgaBspo7mh9Hjd3KtT
@read-the-docs-community

read-the-docs-community Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

@FBumann
FBumann requested review from FabianHofmann and removed request for brynpickering September 15, 2026 10:41
@FBumann

FBumann commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@FabianHofmann This is a cleanup of the code that is breaking for lpspec and linopy-mathspec.

I like the naming much more! Please accept.

EDIT: I would defer this until we merged #437

@FBumann
FBumann marked this pull request as draft September 15, 2026 10:49

FBumann commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Note

The following content was generated by AI.

Follow-up once #437 lands. #437 rewrites lookups as relations and redefines what over= means on the surface: it survives only on shift, sum_back and position, as "the axis walked and kept", while a sum's dims become consume=. Three of this PR's renames already agree with that (Program.lookups keyed by name, over on Translate and WindowSum, Pullback as the read). Four more only make sense on top of #437's semantics, so they wait for it rather than land here:

Not planned: renaming lookups to relations in the program. The file still says lookups:, and the program follows the file's group names.

Merge order. #437 first, then this PR re-applied on top. This diff is a scripted rename over 23 files and is cheap to regenerate; rebasing #437's 84 files over these renames is manual conflict resolution in resolution.py, exclusivity.py, the where parser and the typesetter.


Generated by Claude Code

@FBumann
FBumann removed the request for review from FabianHofmann September 15, 2026 10:50
Re-applies the renames on top of #437, which brought relations in. The
three relation where-leaves drop the `Node` suffix with the rest. Two
follow-ups #437 made possible land with it: `Walk` and
`RelationDeclaration` are frozen dataclasses, the declaration no longer
carries its own name because `Program.relations` keys it, and a `Walk`
carries the name instead. `Sum.over`, `GroupSum.over` and `.into` keep
their names, because the file kept `over=` and `into=`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fhGZgaBspo7mh9Hjd3KtT
…as the file does since #477

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fhGZgaBspo7mh9Hjd3KtT
@FBumann
FBumann marked this pull request as ready for review September 15, 2026 20:25
Brings the branch up to 0b4f046. Deliberately 0b4f046 rather than main: main
also carries #474, which #481 reverts — and #474 adds `walk_regions` to
`program.py`, which this branch renames throughout.

One file conflicted. `tests/test_lowering.py` carries both renames at once:
this branch drops the `Node` suffix, and #449 renamed the gallery's `p` and
`p_max` to `dispatch` and `capacity`. The merged file takes #449's values
with this branch's type names, and its two API decisions hold — the mask
constant is built with `Multiply(Variable(...), Parameter(...))` rather than
the deleted operator sugar, and the domain is read off `program.variables`
rather than the deleted `program.variable()`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
@FBumann

FBumann commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Superseeded by #585

@FBumann FBumann closed this Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants