Skip to content

feat(language): a where may compare arithmetic over parameters - #469

Closed
FBumann wants to merge 5 commits into
mainfrom
claude/zealous-archimedes-k13bq7-arithmetic-where
Closed

FBumann wants to merge 5 commits into
mainfrom
claude/zealous-archimedes-k13bq7-arithmetic-where

Conversation

@FBumann

@FBumann FBumann commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Prompt: Does this enable more coplex where in other places too? […] THen this should probably be directly to main!

Note

The following content was generated by AI.

Either side of a comparison in a where string may now be arithmetic over parameters: p_min <= 0.5 * p_max, sum(p_max, over=generator) >= peak, p_max <= at(bus_cap, by=bus_of), a macro, a named expression. It applies to every where: a variable's, a constraint's, and the assumptions: block of #465, which now stacks on this. Replaces #466.

What this changes

Grammar, resolution, the two vocabularies, rendering

Grammar. The where grammar's comparison atom gains expression COMPARATOR expression, with the expression parser's arithmetic element (ARITHMETIC, now exposed) on each side. The three comparison forms are matched longest-first, so p > 2 * q is not cut short at p > 2; the expression form stands aside for the plain shapes and for position(...), so p > 0 keeps the node its dtype rule is written for. A side is held to the same nesting depth as an expression, measured on the where text.

Resolution. A side is expanded (macros, named expressions), typed, degree-checked and dim-checked exactly as an expression is; the dims stamped on the node are every dim either side carries. Two things an expression may carry are refused here: a variable and a dual(). Namespace now carries the schema, which those checks need. A plain-form comparison that names an expressions: entry on either side takes the expression path.

Two vocabularies. The resolved tree holds ArithmeticComparisonNode over the core syntax tree, which the typesetter, the dim rules and the exclusivity check read. lower_program rebuilds every mask, on variables, constraints and case regions, with ExpressionComparisonNode over program expressions in its place, so a program's masks are program vocabulary throughout. Both are in the closed WhereNode union; the docstring on the first says a program never carries it.

Meaning. A comparison of expressions holds at every coordinate of the dims either side carries, and is held to the frame it sits in like any mask. A side with no value at a coordinate compares false, as a null does in every other comparison; under a summing operator the absent term is one fewer. A shift names its edge= as it does everywhere, and a position() term keeps the vacated row out (the reference shows the example).

Not decided. A case when: that compares expressions is refused as undecidable before the data arrives, with the rewrite, the way an ordered lookup pair is.

Rendering. One branch in the walk, since it already renders arithmetic. names_read on a mask now includes the lookup a grouping or pullback joins through and the parameter a named offset or width is read from, which an engine has to bind.

Not done here. max, min and count are new primitives, taxed as limits.md says. lpspec's mask evaluation gains an expression evaluator in its own PR.

Verified

uv venv, Python 3.12, since pixi is not installable in this environment:

Mutation table

Taken on the stacked branch of #466, which carries this change verbatim on top of #465; each guard deleted in turn, the suite run, the file restored from a copy and the tree checked clean.

Guard Caught by
the expression form standing aside for the plain shapes test_a_where_is_one_resolved_predicate_with_every_literal_folded[…], the gallery pages, the parser tests
the variable refusal on a side [a-variable-inside-arithmetic], test_a_bound_or_where_cannot_name_an_expression[where]
the dual refusal on a side [a-dual-inside-arithmetic]
a named expression on a side of the plain form [a-named-expression-on-the-left], […-right]
the dims stamped on a comparison of expressions test_a_comparison_of_expressions_lowers_to_program_expressions_on_both_sides, the notation page
a case comparing expressions is undecidable test_a_case_comparing_expressions_is_refused_as_undecidable
lowering rebuilds a comparison of expressions test_a_comparison_of_expressions_lowers_to_program_expressions_on_both_sides
Coverage moved
  • test_a_bound_or_where_cannot_name_an_expression[where] asserted that a where naming an expressions: entry fails to resolve; the entry is now read as arithmetic, and the case asserts the variable refusal that body earns instead.
  • The golden fixture gains four constraints whose masks compare expressions (arithmetic; a translation with its edge, a pullback and a position guard; a reduction on a scalar mask; a named expression) and one data-only named expression, so the node censuses see every new arm. ExpressionComparisonNode joins the census's list of nodes the walk never meets, with the walk's guard on it exempted from the line census.

🤖 Generated with Claude Code

https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1


Generated by Claude Code

Either side of a comparison in a where string may be an expression, read as
an expression is: macros and named expressions expand, every operator keeps its
rule, and a variable or a dual is refused. Resolution types it as
`ArithmeticComparisonNode` over the core syntax tree for the spec-side readers;
lowering rebuilds every mask with `ExpressionComparisonNode` over program
expressions, so a program's masks are program vocabulary throughout. The
expression form stands aside for the plain shapes, so `p > 0` keeps its node.

Docs sentence lengths (n / median / over 25): expressions.md 139 / 16 / 27,
reading.md 68 / 15 / 15.

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

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

Copy link
Copy Markdown

@brynpickering brynpickering left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening this. Definitely needed!

side is read exactly as an [expression](#expressions) is, so a macro and a
named expression expand into it and every operator keeps its own rule. Two
things an expression may carry are refused here, because a mask is built before
either exists: a variable, and a `dual()`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and other expressions (at least insofar as those other expressions include variables or duals)?

Comment thread docs/reference/language/expressions.md Outdated
Comment on lines +252 to +256
A case `when:` that compares expressions cannot be proved apart from its
neighbours before the data arrives, so it is refused there with the rewrite:
compare one parameter against a literal, or precompute the test as a boolean
parameter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand this sentence at all.

@FBumann FBumann Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when is the thing that Cases operates on.

So each when separates an expression.

To enable static analysis of the when separations, we disallow complex expressions in when.

This might be feasible, but im not sure if it is.

FBumann and others added 2 commits September 15, 2026 19:13
Brings the branch up to 0b4f046, which carries #437's relations rename and
#449's gallery renames. Deliberately 0b4f046 rather than main: main also
carries #474, which #481 reverts, and merging it here would resurrect it.

Seven files conflicted. The renames main made are taken, and this branch's
new nodes are kept alongside them:

- The frame-check message keeps this branch's `leaf` phrase, which the two
  expression-comparison nodes need because they carry no name, with main's
  wording and `where-relation` spelling for the four named cases.
- `_comparison` takes main's dotted-column body, with this branch's
  `expressions:` check in front of it.
- `_expression_comparison` and main's `_relation_column` are both kept.
- `LookupNode` is `RelationNode`, and a translation's `partition` is now a
  `Walk`, so `names_read` reads `.name` off it.
- The new cases spell `shift`/`sum_back` with `along=` and `window=`.

The generated schema, goldens and pages are regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
FBumann pushed a commit that referenced this pull request Sep 15, 2026
Brings this branch onto #469 as rebuilt on the alpha.90 release, so it picks
up #437's relations rename and #449's gallery renames.

Nine files conflicted, and three more merged cleanly while still written in
the old vocabulary, which was the larger half of the work:

- The typesetting union this branch adds was named `RelationNode`, which
  main now uses for a resolved `by=`. The alias is `AlignedComparison`, and
  `_relation` reads a relation column through `_value_read` and a position
  group through `_position_group`, as main's `_predicate` does.
- `_comparison` keeps main's dotted-column body and this branch's parameter
  pair, which is taken only where neither side names a column.
- `_parameter_pair_error` and main's `_relation_pair_error` are both kept.
- `examples/commitment.yaml` assumed `p_min <= p_max`, which #449 renamed to
  `min_output <= capacity`.
- The new cases spell `shift`/`sum_back` with `along=` and `window=`.
- The expressions page said two parameters cannot be compared, which this
  branch makes false; it now states both pair forms.

The generated schema, goldens and pages are regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
Relations replaced lookups upstream, so the arithmetic comparison lands on
the new plan: the `Lookup*` predicate nodes are `Relation*`, `_names_under`
reads a partition's `Walk` rather than a name, and the golden model's
`ramped` where says `along=`. `_comparison` keeps main's relation-column
reading and takes the expression path first, as before.

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

FBumann commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Superseeded by #566

@FBumann FBumann closed this Sep 20, 2026
@FBumann
FBumann removed this pull request from stack #472 September 20, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: where What a where predicate may say

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants