Repository navigation
Conversation
…h lanes math-spec#566 lets either side of a where comparison be arithmetic over parameters — `p_max > cost`, `0.5 * p_max > 0`, `sum(p_max, over=generator) > 0`. Both lanes now build it, so what the language accepts is what this package builds. A side is compiled the way a constant side of a constraint is: the relational lane folds its constant fragments over the comparison's dims and joins the value column onto the carrier, the linopy lane evaluates it to a DataArray. A coordinate no fragment reaches stays null and the comparison reads false, which is what every other atom over a missing value does. Two architectural decisions to review rather than take on trust: Hard rule 0 moves. A mask now reads an expression, and a cases expression already read a mask, so the recursion is the language's own grammar and no ordering of a lane's two modules removes it. DELIBERATE_LAZY_IMPORTS was empty and that emptiness was the claim; it now holds two entries, one per lane, and architecture.md says so. The alternative was threading an evaluator callback through masked() at seven call sites in two modules, which buys a parameter and a detour for every future reader. NEVER_LOWERED excuses ArithmeticComparisonNode from both dispatch guards. It is in program.WhereNode but lower_program rewrites every one into an ExpressionComparisonNode, so no lowered mask holds one and neither lane can dispatch on it. The exclusion is a claim about upstream, so test_no_never_lowered_node_survives_lowering checks it rather than trusting it, and the two assert_never pragmas name the same reason. Whether the union should carry a node lowering always replaces is a question for #566. Coverage moved: 'p_max > cost' leaves the refusal sweep, where it asserted "compares two parameters", and joins ACCEPTED with the two arithmetic cases, so all three are swept for lane agreement on rows and status. pytest -q -n auto: 4023 passed, 251 skipped, 1 xfailed. ruff check, ruff format and pyrefly clean, the last with 6 suppressed rather than 4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHXujoUG77G2SoDbhau5mS
…ompiled as a constant The constants-only guard in _side survived deletion with the whole suite green, because lower_program refuses a variable on a where side before the engine is reached and no model can carry one there. The probe hands compile_predicate the node directly, so the guard is exercised by the one caller that can reach it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHXujoUG77G2SoDbhau5mS
Merging this PR will not alter performance
Comparing Footnotes
|
Documentation build overview
14 files changed ·
|
FBumann
added this pull request to stack #1698
September 19, 2026 19:05
FBumann
marked this pull request as draft
September 19, 2026 19:06
…ather than crashing the eager one `where: "1 > 0"` reached the eager lane as a Python bool, because both sides evaluate to scalars and the comparison of two scalars carries no dimension. The handler called `.fillna` on it and raised AttributeError, while the relational lane built the model. One language, two answers. The comparison is wrapped as a DataArray before the null fill, which is the 0-dimensional mask `evaluate_where` already returns for the no-mask case, so callers keep combining with `&` and `|` without case analysis. Found by sweeping the expression language across a where side rather than by a report. '1 > 0' joins ACCEPTED, so the lanes are held to agreeing on it the way they are on every other predicate; it fails there without the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHXujoUG77G2SoDbhau5mS
This was referenced Sep 19, 2026
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.
math-spec#566 lets either side of a
wherecomparison be arithmetic over parameters —p_max > cost,0.5 * p_max > 0,sum(p_max, over=generator) > 0. Both lanes build it now, so what the language accepts is what this package builds.A side is compiled the way a constant side of a constraint is: the relational lane folds its constant fragments over the comparison's dims and joins the value column onto the carrier, the linopy lane evaluates it to a
DataArray. A coordinate no fragment reaches stays null and the comparison reads false, which is what every other atom over a missing value does.Two decisions to review rather than take on trust
Hard rule 0 moves. A mask now reads an expression, and a
casesexpression already read a mask, so the recursion is the language's own grammar and no ordering of a lane's two modules removes it.DELIBERATE_LAZY_IMPORTSwas empty and that emptiness was the claim; it now holds two entries, one per lane, anddocs/about/architecture.mdsays so. The alternative was threading an evaluator callback throughmasked()at seven call sites in two modules, which buys a parameter and a detour for every future reader.NEVER_LOWEREDexcusesArithmeticComparisonNodefrom both dispatch guards. It is inprogram.WhereNode, butlower_programrewrites every one into anExpressionComparisonNode, so no lowered mask holds one and neither lane can dispatch on it. The exclusion is a claim about upstream, sotest_no_never_lowered_node_survives_loweringchecks it rather than trusting it, and the twoassert_neverpragmas name the same reason. Whether the union should carry a node lowering always replaces is a question for math-spec#566, and I have not raised it there.Mutation table
tools.mutate, one guard, run before and after the probe that reaches it.predicates.py:223-225)The guard survived because
lower_programrefuses a variable and adual()on a where side before the engine is reached, so no model can carry one there.test_a_where_side_holding_a_variable_is_caught_rather_than_compiled_as_a_constanthandscompile_predicatethe node directly, which is the one caller that can reach it — without it the side would fold a variable's coefficient into a value column and mask on it.Coverage moved
'p_max > cost'leaves the refusal sweep, where it asserted "compares two parameters", and joinsACCEPTEDwith'0.5 * p_max > 0'and'sum(p_max, over=generator) > 0'. All three are swept bytest_both_lanes_build_the_same_modelfor lane agreement on variable rows and termination status.Gates
Run through
uvrather than pixi, which is not installed in this environment — a departure from thepixi run checkdefault. Re-run after the rebase.pytest -q -n autoruff check .ruff format --check .pyrefly checkNot run:
test-bench, the sweep, and the docs build. The typst gallery cases skip for want of the binary, as they do in CI.Deliberately not done: nothing was measured, so no number is claimed — a where side now compiles an expression where it used to read a column, and whether that costs anything on a real model is unmeasured. The stack is the base branch: this sits on #1696, which carries the pin.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NHXujoUG77G2SoDbhau5mS
Generated by Claude Code