Repository navigation
fix(compat): a constant beside a variable term is refused where its data is short - #1621
Merged
Merged
Conversation
…ata is short The eager lane's constant-side coverage check skipped any side that carried a variable, so a constant piece added beside a term — `x + hi <= 100`, where the missing `hi` fills with the zero that is a bound — went unasked and the lane built `x <= 100` in silence. The relational lane caught the simple shape through its compiled fragments but shared the blind spot through a reduction, so `sum(x) + sum(hi) <= 100` was unreported on both. Both lanes now ask their coverage question of every constant piece — a parameter in a variable-free additive position — by threading a coefficient flag through the walk: a parameter a variable stands with in a product is a coefficient, whose missing row is the zero the absence rules allow, and is left alone. Closes #1521. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JyxyehhHvXsBqf6NxeQgU1
Merging this PR will not alter performance
Comparing Footnotes
|
fluxopt-release-bot Bot
added a commit
that referenced
this pull request
Sep 14, 2026
🤖 I have created a release *beep* *boop* --- ## [0.0.1-alpha.329](v0.0.1-alpha.328...v0.0.1-alpha.329) (2026-09-14) ### Bug Fixes * **compat:** a constant beside a variable term is refused where its data is short ([#1621](#1621)) ([545ed6d](545ed6d)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: fluxopt-release-bot[bot] <307443024+fluxopt-release-bot[bot]@users.noreply.github.com>
FBumann
added a commit
that referenced
this pull request
Sep 15, 2026
…ata is short (#1621) > **Prompt:** What about the last bug in lpspec? … Full fix, both shapes. > [!NOTE] > The following content was generated by AI. A constant piece added beside a variable term is now asked for its data on both lanes. `x + hi <= 100` with `hi` short of a coordinate read `x <= 100` on the eager lane — the missing `hi` filled with the zero that is a bound — while the relational lane refused; the two lanes disagreed, and the eager one built the wrong constraint in silence. Both lanes' constant-side check skipped any side that carried a variable, so a piece beside a term went unasked. They now ask of every **constant piece** — a parameter in a variable-free additive position — found by threading a `coefficient` flag through the walk: a parameter a variable stands with in a product is a coefficient, whose missing row is the zero the absence rules allow, and is left alone. Closes #1521. <details><summary>The shapes, and both lanes' verdict</summary> | constraint, `hi`/`c` short one coordinate | before | after | | --- | --- | --- | | `x + hi <= 100` | eager builds `x <= 100`, relational refuses | both refuse, one sentence | | `sum(x) + sum(hi) <= 100` | **both** build (the gap is summed away) | both refuse, one sentence | | `(x + hi) * 2 <= 100` | eager builds, relational refuses | both refuse | | `w * x <= 100`, `w` short | both build (`w=0` is a legal coefficient) | unchanged — still both build | The aggregated shape (`sum(x) + sum(hi)`) is the one #1465 left, missed on both lanes; the eager check asked of a variable-free side and the relational one could not see the hole through the sum. Both now ask the parameter directly. </details> <details><summary>What was verified</summary> - Three tests in `tests/test_absence.py`: both lanes refuse `w * x + c <= 100` and its summed form with one identical sentence (`both_lanes_refuse`), and a sparse coefficient beside a covered piece still builds and agrees (`differential(..., lp=True)`). The two refusal tests fail on the tree before the fix (the eager lane built in silence); confirmed. - Full suite: **3684 passed, 329 skipped**; the 10 failures are all `gurobi` / `xpress` cases, which need commercial solvers absent from this environment — 94 pass with HiGHS across the two affected files. `ruff check` / `ruff format --check` clean; the changed functions add no `pyrefly` error. - **Not run:** `pixi run check` — pixi is unavailable here, so gates ran through a local venv. </details> <details><summary>Mutation table</summary> Each guard deleted in turn, the affected tests run (`tools.mutate` needs a committed tree it did not have mid-work, so taken by hand): | guard removed | caught by | | --- | --- | | the `if not coefficient:` yield-suppression (both walks) | `test_a_sparse_coefficient_is_still_a_zero_coefficient`, `…_beside_a_constant_piece_is_still_a_zero`, `…_on_the_bound_side_still_pins_the_variable` fail | | the `coefficient or carries_variable(…)` propagation through a product | the same three coefficient tests fail | | the widening (restore the whole-side `carries_variable` skip) | `test_a_constant_piece_beside_a_term_is_refused_on_both_lanes`, `…_through_a_reduction` fail | </details> <details><summary>On the stack</summary> Meant as a PR stacked on #1536's branch, but [#1615](#1615) merged to `main` first, so this targets `main` directly — the base already carries #1536, and the diff is this fix alone. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01JyxyehhHvXsBqf6NxeQgU1 --- _Generated by [Claude Code](https://claude.ai/code/session_01JyxyehhHvXsBqf6NxeQgU1)_ Co-authored-by: Claude <noreply@anthropic.com>
FBumann
pushed a commit
that referenced
this pull request
Sep 15, 2026
🤖 I have created a release *beep* *boop* --- ## [0.0.1-alpha.329](v0.0.1-alpha.328...v0.0.1-alpha.329) (2026-09-14) ### Bug Fixes * **compat:** a constant beside a variable term is refused where its data is short ([#1621](#1621)) ([545ed6d](545ed6d)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: fluxopt-release-bot[bot] <307443024+fluxopt-release-bot[bot]@users.noreply.github.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.
A constant piece added beside a variable term is now asked for its data on both
lanes.
x + hi <= 100withhishort of a coordinate readx <= 100on theeager lane — the missing
hifilled with the zero that is a bound — while therelational lane refused; the two lanes disagreed, and the eager one built the
wrong constraint in silence.
Both lanes' constant-side check skipped any side that carried a variable, so a
piece beside a term went unasked. They now ask of every constant piece — a
parameter in a variable-free additive position — found by threading a
coefficientflag through the walk: a parameter a variable stands with in aproduct is a coefficient, whose missing row is the zero the absence rules allow,
and is left alone.
Closes #1521.
The shapes, and both lanes' verdict
hi/cshort one coordinatex + hi <= 100x <= 100, relational refusessum(x) + sum(hi) <= 100(x + hi) * 2 <= 100w * x <= 100,wshortw=0is a legal coefficient)The aggregated shape (
sum(x) + sum(hi)) is the one #1465 left, missed on bothlanes; the eager check asked of a variable-free side and the relational one
could not see the hole through the sum. Both now ask the parameter directly.
What was verified
tests/test_absence.py: both lanes refusew * x + c <= 100and its summed form with one identical sentence (
both_lanes_refuse), and asparse coefficient beside a covered piece still builds and agrees
(
differential(..., lp=True)). The two refusal tests fail on the tree beforethe fix (the eager lane built in silence); confirmed.
gurobi/xpresscases, which need commercial solvers absent from this environment —94 pass with HiGHS across the two affected files.
ruff check/ruff format --checkclean; the changed functions add nopyreflyerror.pixi run check— pixi is unavailable here, so gates ran througha local venv.
Mutation table
Each guard deleted in turn, the affected tests run (
tools.mutateneeds acommitted tree it did not have mid-work, so taken by hand):
if not coefficient:yield-suppression (both walks)test_a_sparse_coefficient_is_still_a_zero_coefficient,…_beside_a_constant_piece_is_still_a_zero,…_on_the_bound_side_still_pins_the_variablefailcoefficient or carries_variable(…)propagation through a productcarries_variableskip)test_a_constant_piece_beside_a_term_is_refused_on_both_lanes,…_through_a_reductionfailOn the stack
Meant as a PR stacked on #1536's branch, but #1615
merged to
mainfirst, so this targetsmaindirectly — the base alreadycarries #1536, and the diff is this fix alone.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JyxyehhHvXsBqf6NxeQgU1
Generated by Claude Code