Skip to content

fix(compat): a constant beside a variable term is refused where its data is short - #1621

Merged
FBumann merged 1 commit into
mainfrom
claude/constant-piece-beside-a-term
Sep 14, 2026
Merged

FBumann merged 1 commit into
mainfrom
claude/constant-piece-beside-a-term

Conversation

@FBumann

@FBumann FBumann commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

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.

The shapes, and both lanes' verdict
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.

What was verified
  • 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.
Mutation table

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
On the stack

Meant as a PR stacked on #1536's branch, but #1615
merged to main first, so this targets main directly — the base already
carries #1536, and the diff is this fix alone.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JyxyehhHvXsBqf6NxeQgU1


Generated by Claude Code

…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
@codspeed

codspeed Bot commented Sep 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 58 skipped benchmarks1


Comparing claude/constant-piece-beside-a-term (6f64147) with main (089f816)2

Open in CodSpeed

Footnotes

  1. 58 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on main (59391f3) during the generation of this report, so 089f816 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@FBumann
FBumann merged commit 545ed6d into main Sep 14, 2026
12 checks passed
@FBumann
FBumann deleted the claude/constant-piece-beside-a-term branch September 14, 2026 13:30
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

a constant piece beside a term on the same side is asked for its data by one lane only

2 participants