Conversation
Documentation build overview
25 files changed ·
|
FBumann
added this pull request to stack #560
September 19, 2026 17:35
FBumann
removed this pull request from stack #560
September 19, 2026 18:10
FBumann
added this pull request to stack #574
September 19, 2026 18:10
FBumann
removed this pull request from stack #574
September 19, 2026 18:12
FBumann
added this pull request to stack #575
September 19, 2026 18:12
FBumann
pushed a commit
that referenced
this pull request
Sep 19, 2026
#567 is closed and this branch retargets onto #566. The typing work it carried in its history is on main as #572, so model.py takes main's narrowing: `ValidationInfo[object]` and `dict[str, object]` rather than this branch's unparameterised `ValidationInfo` and pydantic's `JsonSchemaValue`. The two piecewise conflicts are this branch's own feature against the code it renamed: the breakpoint dim is `along`, and a weight's where is the block's where and its mask together. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2SFmoxEF3SnaPKp7HbZTk
FBumann
pushed a commit
to fluxopt/specsolve
that referenced
this pull request
Sep 19, 2026
…fused, as a sum already was math-spec moves from 0.0.0-alpha.105 to c25984e, the head of energy-models/mathspec#566. The pin is a commit rather than a tag because that PR is open and stacked on two more; it is not a pin to merge on. The refusal is the outcome a model author sees. at()'s joined-column check asked whether a joined dimension was produced, which the landing check already refuses, rather than whether it was consumed, so `at(load, by=diag, over=rep, into=generator)` loaded and landed with the joined dimension gone. sum already refused the same shape. Carrying it is a port of the relation node API, which no consumer can tell apart. Walk is now Direction, with Partition split off as its own class rather than a direction with nothing consumed or produced, and the accessors that inverted between the two operators are gone: a node's ends are read off its direction, and an At reads the direction the other way round at the four sites that care. GroupSum.walk and At.walk are .direction; .over, .into, .joined and .relation are .direction.consumed_dims, .produced_dims, .joined_dims and .name, each read in the operator's own terms. Coverage moved: test_by_takes_a_relation asserts position(by=)'s new refusals — a parameter and an undeclared name now take the message every operator's by= gives, and a dimension is told by= takes the map out of it — and test_a_call_walks_one_table_and_says_so follows "walks one table" to "reads one table". The thirteen generated pages are tools.gallery_math and tools.ladder re-run; the diff is one legend line per page, "the order shift walks" becoming "the order shift steps along". Not done here: math-spec#566 also adds ExpressionComparisonNode and ArithmeticComparisonNode, which neither lane evaluates. Four tests are red on that and land with the evaluator in the PR stacked on this one, as #566's own body says they should. pytest -q -n auto: 4016 passed, 251 skipped, 1 xfailed, 4 failed — the four above. ruff check and ruff format clean. pyrefly reports 2 errors, both assert_never on those same two nodes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHXujoUG77G2SoDbhau5mS
This was referenced Sep 19, 2026
FBumann
removed this pull request from stack #575
September 20, 2026 20:34
FBumann
added this pull request to stack #583
September 20, 2026 20:35
…ession grammar's arithmetic The where grammar's three comparison rules — a name against a literal, two relation columns, and position() against an integer — are one rule, side <op> side, where a side is the expression grammar's ARITHMETIC. What a side is, resolution decides with the schema in hand, and it hands back the same typed nodes with the same messages. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
…als it points at Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
…ather than Any `explicit-any` is an error on `src/math_spec` since #572, and that PR took only the fold's return type from #567, leaving `_nested` on this branch with a signature whose `Any` no longer even imports. `_ParsedWhere` is the union the measurement walks, which is #567's own answer for this file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2SFmoxEF3SnaPKp7HbZTk
Either side of a where comparison may be an expression over parameters: arithmetic, a reduction, a pullback, a translation with its edge, a macro, a named expression. A side is expanded, typed, degree-checked and dim-checked as an expression is, and refused where it names a variable or a dual. Two parameters compare the same way. The resolved tree holds ArithmeticComparisonNode over the core syntax tree, and lowering rebuilds every mask with ExpressionComparisonNode over program expressions. A case when: comparing expressions is refused as undecidable before the data arrives. expressions.md sentence lengths: n 68, avg 14.0, median 13, over 25 words 3 (base: n 56, avg 13.0, median 13, over 25 words 2). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
… once, on the program's form The resolved form of the comparison had a second walk collecting the parameters and relations its sides read, and nothing asks a resolved mask that question: lowering rebuilds every mask before one reaches a consumer. The arm is the assertion the typesetter already carries for the lowered node. The one-line mask wrapper in lowering is inlined, and the two validation classes for what a where side may be are one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
…it, so nothing passes both Namespace(schema) reads every declaration at construction; the eight argument constructor and the classmethod that was its only caller are gone. expression_of, _check_expression and _named took the schema and a namespace built from that same schema; they take the namespace. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
…e their only callers expression_of and where_of had no caller in the package, and the two assertions that named expression_of as the door now name resolve_expression, which is the one the package walks through. The plain form of a where comparison is read by one method: a name that is a value against another is arithmetic, which used to be a second question asked after the first. The "prefix a context once" rule has one home in errors.py. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
FBumann
force-pushed
the
claude/expression-parser-language-split-kdqegz-arithmetic-where
branch
from
September 20, 2026 20:51
948a2f9 to
7984556
Compare
FBumann
removed this pull request from stack #583
September 20, 2026 20:53
FBumann
changed the base branch from
claude/expression-parser-language-split-kdqegz
to
main
September 20, 2026 21:15
FBumann
added this pull request to stack #587
September 20, 2026 21:15
… states #585 renamed the program's where vocabulary and expression nodes on main. This branch was written against the old names, and its own two comparison nodes arrived with the suffix the rule reserves for the core AST. Applying the same rename here first is what lets the base merge that follows agree line for line rather than conflict on every one of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPqtQuarRGDmX2WrNVuKDP
#585 landed the program naming rule on main, and #584 cut alpha.107. The branch's own two comparison nodes arrive under that rule as `ExpressionComparison` and `ArithmeticComparison`. Resolved by hand where the two sides changed the same code: - `_check_where_dims` keeps this branch's `leaf` form. main's `noun` form cannot name a comparison of expressions, which carries no single name. - `_declared_rhs_error` keeps this branch's three kinds. A parameter on the right-hand side is arithmetic here, not a refusal, so main's `parameter` branch has nothing left to fire on. - `Namespace` keeps its schema-carrying form and builds a `RelationDeclaration` without the name main dropped from it. - `expression_of` and `where_of` stay in `tests/fixtures.py`, where this branch moved them; main's copies in `resolution.py` go with the merge. - `Translate.along` and `WindowSum.along`, `Program.roots`, `Footprint.kinds` and the relation records are main's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPqtQuarRGDmX2WrNVuKDP
FBumann
added a commit
that referenced
this pull request
Sep 20, 2026
#585 landed this branch's renames on main, and the two branches below carried them up. What is left here is the paragraph saying lowering rewrites every `ArithmeticComparison` into an `ExpressionComparison`, which needs #566's two nodes to be true, and two docstrings. Both conflicts were wording the base branch already carries: the row-building trees are `roots`, and the golden test's carrier note names a direction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPqtQuarRGDmX2WrNVuKDP
…carries The `Predicate` union said it was what a lowered mask's `root` is built of. That is false here: it also holds `ArithmeticComparison`, which lowering rewrites into an `ExpressionComparison`, so a consumer walking a program meets every other member and never that one. These lines were #580's, which is where they were written. They are only true where the two nodes exist, and this is the branch that adds them. docs/reference/reading.md: the two sentences added are 11 and 9 words. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPqtQuarRGDmX2WrNVuKDP
…mparison A consumer walking a lowered mask gets ParameterComparison for `p_max > 5` and ExpressionComparison for `1 * p_max > 5`, though both mask the same coordinates. The page named only the second form. Gates: pytest tests/test_docs.py tests/test_reading_page.py (34 passed), the full suite (1383 passed, 1 skipped), mkdocs build --strict (clean, with the docs.python.org inventory dropped for the run, which this environment's proxy refuses with a 403), prettier --check (clean). Not run: typos, reuse lint, taplo, zizmor, compile-tex, for want of the tools. docs/reference/reading.md, by the measurement in the docs-writing skill: n 55, avg 14.6, median 13, over25 7. Every long sentence predates this change; the three added are 10, 20 and 9 words. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJfEUsCDtwXuXV8CANCHuR
FBumann
removed this pull request from stack #587
September 20, 2026 21:35
FBumann
added this pull request to stack #588
September 20, 2026 21:35
This was referenced Sep 20, 2026
FBumann
removed this pull request from stack #588
September 21, 2026 16:15
FBumann
added this pull request to stack #601
September 21, 2026 16:27
…gz-arithmetic-where Brings the stack up to date with main at 0.0.0-alpha.110. The five commits are three release chores, the relations page, and the fix for a read that lands outside the key. Merged clean. `resolution.py` is the one file both sides changed, and the suite is green on the result: 1390 passed, 5 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EQHKbwGtpVBm6yKHzrjMx9
FBumann
pushed a commit
that referenced
this pull request
Sep 21, 2026
…ic-where' into claude/adoring-brahmagupta-ovwe3v Carries main at 0.0.0-alpha.110 up the stack from #566. Merged clean, and the suite is green: 1414 passed, 5 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EQHKbwGtpVBm6yKHzrjMx9
…ng expressions, are refused at load A comparison with a number on each side is decided before any data arrives, and reached the program as an ExpressionComparison over two constants. A cases: block of one case escaped the undecidable refusal, which only looked at pairs. names_read on a mask over a cased entry dropped the data its regions are decided by. The relation refusal still said every other comparison tests a name against a literal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tCWoetBmjF1LpbTbjQY29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
The following content was generated by AI.
On
main, now that #565 and #585 have landed. Either side of a where comparison may be arithmetic over parameters:p_min <= 0.5 * p_max,sum(p_max, over=generator) >= peak, a pullback, a translation with its edge, a macro, a named expression. This is #469's semantics on the one-atom grammar, and it replaces #469.What this changes
_comparisonsends a comparison to_expression_comparisonwhere a side is not a plain leaf, or where a plain-looking side names a parameter or anexpressions:entry against a value. There each side is expanded, typed, degree-checked and dim-checked as an expression is, and refused where it names a variable or adual(). The transient "arithmetic on a side" refusal chore(language): a where comparison is one grammar rule over the expression grammar's arithmetic #565 introduced is gone, and so is the grammar-side arbitration feat(language): a where may compare arithmetic over parameters #469 needed (_reads_arithmeticas a parse condition,_is_plain,_nestedover two vocabularies, the re-route inside_comparison).c <= 0.5 * kand kept refusingc <= kwith "compares two parameters, which is not in the language". Herec > kloads as a comparison of expressions, and that branch of_declared_rhs_erroris deleted. Astrorboolparameter on a side is refused by the message arithmetic already has for it.2 < 1, is refused and the message names the rewrite; it used to reach the program as anExpressionComparisonover two constants. Acases:block of one case whosewhen:compares expressions is refused as a pair is: the rule is on the case, since itsotherwiseis the negation and only the data says where that falls. The pairwise check reported it only from two cases up.ArithmeticComparisonover the core syntax tree, which the typesetter, the dim rules and the exclusivity check read.lower_programrebuilds every mask, on variables, constraints and case regions, withExpressionComparisonover program expressions.names_readon a program mask includes the relation a grouping or a pullback reads through, the one a translation or window is partitioned by, the parameter a named offset or width is read from, and the masks that decide which region of a cased entry applies. The resolved form does not answernames_read: nothing asks a resolved mask that question, so its arm is the assertion the typesetter already carries for the lowered node rather than a second walk.expand,dims_ofand theexpressions:names, none of which the flat listing holds. feat(language): a where may compare arithmetic over parameters #469 hung the schema onNamespacebehind an eight-argument constructor and a classmethod that was its only caller. HereNamespace(schema)reads every declaration at construction, andexpression_of,_check_expressionand_named, which took the schema beside a namespace built from that same schema, take the namespace alone.lk.h > 2 * kand2 * k == 'x'are refused with the rewrite. The relation refusal no longer claims every other comparison tests a name against a literal.Predicateunion says which member a program never carries. It holdsArithmeticComparison, which lowering rewrites away, so a consumer walking a program meets every other member and never that one. The union's comment and the reading page say so. These eleven lines were written on chore(program): nothing left — this PR's work is in #585, #566 and #569 #580; they moved here because they are only true where these two nodes exist.p_max > 5lowers to aParameterComparisonand1 * p_max > 5to anExpressionComparison, though both mask the same coordinates. Routing prefers the plain path, so no file produces both for one text, but a consumer that handles only the new node misses the common case. Verified by lowering both against the dispatch fixture.expressions.md; three paragraphs onreading.md. The sentence "comparing two parameters is not in the language" is replaced.The break
PredicateandTypedPredicategainExpressionComparisonandArithmeticComparison, and a consumer's mask walk meets the first.Namespace(schema)replacesNamespace.of(schema), andexpression_of(text, ns, context)no longer takes the schema and has moved totests/fixtures.py, its only caller. lpspec's mask evaluation gains an expression evaluator in its own PR, as #469 said. The node renames this branch carries are #585's, already onmain, and lpspec owes them once rather than twice.Measured against
fluxopt/lpspecatfc91b4a, which pinsv0.0.0-alpha.106: both of its mask walkers,linopy/where.pyand the polarspredicates.py, end inassert_neverand have zero branches for either new node.assert_neveris a static check, so each walker needs anExpressionComparisonbranch or it stops type-checking — two branches, not one evaluator's worth of work each, but they are not optional.Merged onto main at
e6ae307, and what that tookMerged, not rebased: the hard rule here is never to force-push.
#585 renamed the program's where vocabulary and expression nodes on
mainwhile this branch was building, and this branch's own two comparison nodes carried theNodesuffix the rule reserves for the core AST. The rename was applied here first, as one commit, so that the base merge agreed line for line instead of conflicting on every one of them: merged straight it conflicted in 40 places.What the two sides genuinely disagreed about, resolved by hand:
_check_where_dimskeeps this branch'sleafform.main'snounform cannot name a comparison of expressions, which carries no single name._declared_rhs_errorkeeps its three kinds. A parameter on the right-hand side is arithmetic here, not a refusal, somain'sparameterbranch has nothing left to fire on.Namespacekeeps its schema-carrying form and builds aRelationDeclarationwithout the namemaindropped from it.expression_ofandwhere_ofstay intests/fixtures.py;main's copies inresolution.pygo with the merge.Translate.along,WindowSum.along,Program.roots,Footprint.kindsand the relation records aremain's.Gates
pixi.shis refused by this environment's egress proxy with a 403, so the gates ran from auvenvironment on Python 3.12 with the pinnedruff==0.16.1andpyrefly==1.2.0. That is a departure from the "every command runs underpixi run" default.pytest -q -n 4main: 1357 passed)ruff check,ruff format --checkpyrefly checkmkdocs build --strictdocs.python.orginventory dropped for the run, which the proxy refuses with a 403prettier --checkon every Markdowntypos,reuse linttaplo,zizmor,compile-texThe golden
.outfiles and the notation page were regenerated and read; the four new constraints print as #469's did. The schema did not move. The YAML example onexpressions.mdwas written to a file and loads withpython -m math_spec check.The reading-page paragraph was added in a later commit and gated on its own:
pytest tests/test_docs.py tests/test_reading_page.py(34 passed), the full suite (1383 passed, 1 skipped),mkdocs build --strictandprettier --checkclean.typos,reuse lint,taplo,zizmorandcompile-texwere not run for it, for want of the tools. The page, by the measurement in the docs-writing skill: n 55, avg 14.6, median 13, over25 7 — every long sentence predates it, and the three added are 10, 20 and 9 words.The review commit (
84e3ec2) was written as four failing tests first:two-numbersandarithmetic-over-numbers-aloneintest_a_bad_comparison_of_expressions_is_refused_at_load,test_a_lone_case_comparing_expressions_is_refused_too, andtest_a_cased_side_reads_the_data_its_regions_are_decided_by. Twotest_exclusivitycases now see one refusal per case rather than one per pair.Coverage moved
test_a_bound_or_where_cannot_name_an_expression[where]asserted that a where naming anexpressions:entry fails to resolve; the entry is now read as arithmetic, and the case asserts the variable refusal that body earns instead.TestAWhereSideIsReadInResolutionholds every case on what a where side may be: theposition()shapes and the literal forms from chore(language): a where comparison is one grammar rule over the expression grammar's arithmetic #565, and the arithmetic cases, withtwo-parametersanda-literal-on-the-leftas loading cases.where-against-a-parameterinTestRulesDecidedWithoutDataasserted "compares two parameters"; it isa-flag-against-a-parameter, asserting theboolrefusal.position()inside arithmetic.Left alone
2 < cloads, where feat(language): a where may compare arithmetic over parameters #469 refused it at parse. Either side may be an expression, and a literal is one.c > cloads. It is unsatisfiable on finite data, but detecting it is tautology detection, which the fold does not attempt.position(g) + 1 == 0is refused as an unknown operator, sincepositionis a where atom and not an expression operator.position()stays out of the operator table: a row there also puts it inBUILTIN_NAMES, which the expression resolver, the unknown-operator message and the golden operator census read.Namespacekeeps its flat fields. They are read in 24 places in the resolver, and reading them off the schema at each site would trade one dict lookup for a walk of a section.ParameterComparisoninto the arithmetic form would lose the literal's type check against the declareddtype, has no representation for thestrcase at all, and would cost lpspec the_certain_namesoptimisation, which reads the node'snamefield to choose an inner join over a left join.🤖 Generated with Claude Code
https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
https://claude.ai/code/session_01JPqtQuarRGDmX2WrNVuKDP
https://claude.ai/code/session_01JJfEUsCDtwXuXV8CANCHuR
https://claude.ai/code/session_019tCWoetBmjF1LpbTbjQY29