refactor(program): a comparison of expressions is one node before and after lowering - #631
Merged
FBumann merged 1 commit intoSep 23, 2026
Conversation
Documentation build overview
14 files changed ·
|
FBumann
added this pull request to stack #637
September 22, 2026 20:48
FBumann
force-pushed
the
claude/blissful-heisenberg-z5rpwo-arith
branch
from
September 23, 2026 08:16
d51ea73 to
b279760
Compare
… after lowering ArithmeticComparison was ExpressionComparison with its sides still in the core syntax tree. ExpressionComparison now takes its side type as a parameter, and program.ArithmeticComparison is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
FBumann
force-pushed
the
claude/blissful-heisenberg-z5rpwo-arith
branch
from
September 23, 2026 11:25
b279760 to
9a5d38d
Compare
FBumann
removed this pull request from stack #637
September 23, 2026 11:30
FBumann
added this pull request to stack #644
September 23, 2026 11:32
FBumann
added a commit
to fluxopt/specsolve
that referenced
this pull request
Sep 23, 2026
…at() (#1718) > **Prompt:** Update lpspec to the latest mathspec release (119) > [!NOTE] > The following content was generated by AI. The pin moves from math-spec alpha.116 to alpha.119. Both lanes now build the new `at(<predicate>, by=, over=, into=)` in a `where:` (energy-models/mathspec#634), and they agree on the result. A coordinate with no relation row reads false. 70 insertions, 34 deletions. <details><summary>What moved</summary> * **`PulledBackPredicate`**: the relational lane walks the coordinates the operand admits through `walk_join`, as an expression's `at` does. The linopy lane reads the evaluated mask through `operator_at`. Before this change, both lanes hit `assert_never`. * **`PiecewiseExpansionError` is gone upstream** (energy-models/mathspec#638). A piecewise block now raises `DimensionError`, so `lps.PiecewiseExpansionError` goes too, with no alias. `test_api`, `test_architecture` and `test_piecewise` follow, and so does `docs/reference/api.md`. * **`ArithmeticComparison` is gone upstream** (energy-models/mathspec#631). The two lanes' branches for it were never reached, so they go. `NEVER_LOWERED` in `test_resolution_parity` goes with them. * **Tests**: `test_a_where_reads_a_relation` gains 2 cases and `test_a_relation_where_agrees_with_the_oracle` gains 3: a total relation, a partial one, a negation and a conjunction. `COVERED_ELSEWHERE` names the oracle test for `PulledBackPredicate`. Before the implementation, the coverage guard failed on it. * **Upstream now refuses `sum()` over a scalar**, so the carried-parameter probe in `test_strategy` reads `soc_initial` bare. It still asserts the refusal names "carried". * `uv.lock` is relocked. Only the math-spec entry changed. </details> <details><summary>Mutation table</summary> Run by hand on the committed tree. Each file was restored through `git checkout --` and `__pycache__` was dropped. The runs cover `test_label_coords.py` and `test_resolution_parity.py`. | Mutation | Result | | --- | --- | | linopy: a missing relation row reads true (`fillna(True)`) | caught, 1 failed | | polars: a missing row reads true (`fill_null(True)`) | caught, 5 failed | | polars: the operand's mask is ignored (`masked(..., None)`) | caught, 5 failed | </details> <details><summary>Gate</summary> ``` ruff check . clean ruff format --check . 327 files already formatted pyrefly check 0 errors (20 suppressed) pytest -q -n auto 3796 passed, 403 skipped, 1 xfailed, 11 failed ``` The run used `uv` with the `[linopy]` extra, not pixi. The xfail is the known `osemosys_utopia` / #894 case. Of the 11 failures, one was the `test_strategy` probe, which is fixed in the second commit. The other 10 are the gurobi and xpress parametrizations of `test_diagnostics` and `TestThePositionalHandoff`. They raise `ModuleNotFoundError` because neither package is installed here, and they fail the same way on `origin/main`. **Not run**: `docs-build`, `docs-test`, `test-floors`, `test-bench`, the gurobi and xpress sinks. I did not regenerate the gallery pages. The typesetter changed upstream (energy-models/mathspec#629), but the doc tests passed. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LzN7uYEvHW9dDGCmtAzbkD --- _Generated by [Claude Code](https://claude.ai/code/session_01LzN7uYEvHW9dDGCmtAzbkD)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
program.ArithmeticComparisonis deleted. It wasExpressionComparisonwith its sides still in the core syntax tree.ExpressionComparison[Side]is now one class: its sides are syntax-tree nodes before lowering andExpressions after. A consumer meets one comparison class. Stacked on #629.Method, gate output, alternatives
What this changes
ExpressionComparisontakes its side type as a type parameter. Resolution buildsExpressionComparison[ArithmeticNode]. Lowering rebuilds it withExpressionsides.ArithmeticComparisonleavesprogram.__all__,PredicateandTypedPredicate.docs/reference/reading.mddrops the sentence "one member of thePredicateunion never reaches you".Breaking for consumers
program.ArithmeticComparisonno longer exists. There is no alias, per the alpha-stream rule. fluxopt/lpspec has three references, and each only refuses the class:src/lpspec/linopy/where.py:125-127: delete theArithmeticComparisonarm.src/lpspec/relational/engines/polars/predicates.py:232-234: delete theArithmeticComparisonarm.tests/test_resolution_parity.py:91-94,152: deleteNEVER_LOWERED.expectedbecomesset(get_args(program.Predicate)).This session could not open that PR, because push access to lpspec was not granted.
Guards removed
These two
AssertionErrors can no longer tell the two states apart. Nothing reaches either one today:Mask.names_read's "a resolved mask is asked what it reads".ExpressionComparisonis out of the golden test'sUNRESOLVEDset, because the walk now renders it.Why a type parameter and not a private class
Mask.atomsandMask.dimsrun on resolved masks: in the dim rules, exclusivity and validation. Soprogramhas to know the resolved comparison either way. One class with a side type is one concept. A private second class is two.A type-parameter default (
[Side = Expression]) needs Python 3.13, and the package supports 3.12. So the two union aliases that serve asisinstancetargets carry apyrefly: ignore[implicit-any-type-argument], with the reason given inline.Gates
pixi run lint: green.pixi run test: 1560 passed.docs-buildandcompile-texdid not run: the session proxy blocks their downloads.🤖 Generated with Claude Code
https://claude.ai/code/session_017XwKgY5wZXv1bKgkfCW2q1
Generated by Claude Code