Conversation
…h lanes Follows math-spec to 5e29391, the head of energy-models/mathspec#469, where either side of a where comparison may be an expression: `p_min <= 0.5 * p_max`, `sum(p_max, over=generator) >= peak`, `load - shift(load, over=snapshot, offset=1, edge=0) > ramp`. The relational lane compiles each side as a constant position and adds its pieces per coordinate, null where no piece has a value, then joins the column onto the mask's carrier. The linopy lane evaluates each side as a build does, absent rows filled with zero, and masks the comparison by where the side has a value, decided by structure off the plan. The two linopy-lane walks now live in one module, `linopy/evaluation.py`: a cased expression holds a mask per region and a mask may compare expressions, so the expression walk and the where walk are mutually recursive. `builder.py` keeps the declarations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1
…are probed on their own The two guards a green suite could not reach: the sum rule is probed by a comparison the present term alone satisfies, and the edge rule by a bare shift compared, with no other term to lend the side a value. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1
Merging this PR will not alter performance
Comparing Footnotes
|
FBumann
added this pull request to stack #1643
September 15, 2026 11:48
The pin follows math-spec's own branch onto the relations plan, so the evaluation module reads `Relation*` predicate nodes, a partition's `Walk` and `maps_out_of`, and the tests that spell a `shift` or a `sum_back` say `along=` and `window=`. `where.py` stays deleted: what main changed in it lands in `evaluation.py`, which holds both walks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pdMgEVkGBfh1f2AyiCSKA
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.
Either side of a
wherecomparison may be an expression on both lanes —p_min <= 0.5 * p_max,sum(p_max, over=generator) >= peak,load - shift(load, over=snapshot, offset=1, edge=0) > ramp— read by the absence rules the language states: a side with no value compares false, and under a+, a sum or a window an absent term is one fewer.Follows math-spec to
5e29391, the head of energy-models/math-spec#469. The second PR of the stack pins the assumptions branch on top of this one.How each lane reads a side
PolarsCompiler.expression), its pieces added per coordinate withadded(..., fill=False)— null where no piece has a value — and the column joined onto the mask's carrier, one column per side. A null compares as null, and the consumer'sfalsy_if_nullreads it as false, as for every other comparison.evaluate_expression's value, absent parameter rows filled with zero (right for every piece that adds), masked by where the side has a value, decided by structure off the plan (_has_value): a sum where any term has one, a product or a quotient where both factors do, an operator wherever it gathers a present slot — the indicator is put through the same operator, a shift's named edge counting as a value and a window's unreachable lag not.linopy/where.pyis nowlinopy/evaluation.py, holding both walks. A cased expression holds a mask per region and a mask may now compare expressions, so the expression walk and the where walk are mutually recursive;builder.pykeeps the four declarations.test_lazy_intra_package_imports_are_all_declaredis why: a deferred import between the two would have been the first exception to hard rule 0.ARCHITECTUREand the linopy page follow.ArithmeticComparisonNode, the spec-side node math-spec'sWhereNodeunion also carries, is asserted away in both walks: lowering rebuilds every mask with anExpressionComparisonNodein its place, so no lane reads one. The two censuses intest_architecture.pysee it named; the parity census excludes it.Mutation table
Taken by hand — each mutation is a changed expression
tools/mutate.pycannot express — with the tree restored throughgit checkout --and__pycache__dropped on both sides; the tree came back clean each time. Suite:tests/test_where_arithmetic.py tests/test_resolution_parity.py, base70f0e6defor the first pass anda6facab3for the re-run.& left_defined & right_defined|→&in_has_value'sAddp_max + extra > 50)&→|inMultiplyfill=Noneshift(...) < 85)added(..., fill=True)The two survivors were survivors because the first table's cases let another term lend the side a value; the second commit adds the two probes.
Verified
Ran in a uv venv (pixi is not installable here) with the linopy extra and math-spec editable at
5e29391:ruff check .,ruff format --check .: clean.pyrefly check --python-interpreter-path <venv>: 0 errors (the barepyrefly checkreports 142 import errors onmaintoo — it cannot see the venv).pytest -n 8: 3751 passed, 329 skipped, 1 xfailed; 12 failed, all[gurobi]/[xpress]cases refusing because neither extra is installed here (the same 10 fail onmain; the remaining two were the census tests this PR makes pass, and the cased-expression test fixed before the second commit).Not run:
pixi run checkas such, the docs build,test-bench.Not done, and departures
docs/reference/data.md: nothing new is refused at the door. The linopy page'swheretable gains one row.!=reading of a NaN in the existingParameterComparisonNodebranch of the linopy lane (p != 3wherephas no row reads as true, where the relational lane reads null as false) is adjacent and untouched; the new branch masks by presence explicitly, so it does not inherit it._has_value, and the parity caseload / extra > 50passes underfilterwarnings = error.🤖 Generated with Claude Code
https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1
Generated by Claude Code