Skip to content

test(compat): a divisor written as a power is named and refused on both lanes - #1615

Merged
FBumann merged 1 commit into
mainfrom
claude/tender-thompson-ko231z
Sep 14, 2026
Merged

FBumann merged 1 commit into
mainfrom
claude/tender-thompson-ko231z

Conversation

@FBumann

@FBumann FBumann commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Prompt: Lets work on #441 and #1536

Note

The following content was generated by AI.

A test, on both lanes, pinning that a divisor written as a power is named and
refused. p / (d ** 2) <= 1 with d missing a row used to split the lanes, and
neither answer was usable.

The cause was upstream: program.children in math-spec had no branch for a
power, so d ** 2 hid d from every walk this package asks for a divisor's
names. The relational lane named the divisor ''; the eager lane found no
divisor to check, filled the gap with a zero coefficient and built inf into
the matrix with no refusal at all.

That was fixed upstream in energy-models/math-spec#403
(PR #404, in the pinned alpha.85). What this tree owed is the guard — the gap
was never in the language's coverage rules, but in what this package asks the
language. Both lanes now name d and refuse in one identical sentence.

Closes #1536. The issue's blocked:upstream label is now stale — the blocker
merged.

What was verified
  • New test test_a_sparse_divisor_written_as_a_power_is_named_and_refused_on_both_lanes
    in tests/test_arithmetic_laws.py, beside the existing divisor tests, using
    both_lanes_refuse (which builds the two lanes apart, so an eager lane that
    silently built inf would fail the raise rather than pass unnoticed).

  • Confirmed against a simulated pre-perf(duckdb): the factored label path, and one answer for which route to take #403 children (the Power branch dropped):
    the test fails, so it exercises the gap rather than passing on a broken tree.
    Against the pinned math-spec it passes; both lanes return:

    constraint 'c': parameter 'd' is used as a divisor but covers 1 fewer coordinates than it is indexed over. …
    
  • tests/test_arithmetic_laws.py: 26 passed (both lanes, linopy from git
    master). ruff check and ruff format --check clean.

  • Not run: pixi run check — pixi is unavailable here, so the tests and
    linters were run through a local venv. This is a test-only change, so src/
    and the schema are untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JyxyehhHvXsBqf6NxeQgU1


Generated by Claude Code

…th lanes

`p / (d ** 2) <= 1` with `d` missing a row used to split the lanes: the
relational lane named the divisor `''` and the eager lane found no divisor to
check, filled the gap with a zero coefficient and built `inf` into the matrix
with no refusal. The cause was a walk in math-spec that had no branch for a
power, so `d ** 2` hid `d`.

That was fixed upstream (energy-models/mathspec#403, in the pinned alpha.85).
What this tree owed is the guard that both lanes now name `d` and refuse in one
sentence — the gap was never in the language's coverage rules but in what this
package asks the language.

Closes #1536.

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/tender-thompson-ko231z (11ca734) with main (8d27e88)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 (6260826) during the generation of this report, so 8d27e88 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@FBumann
FBumann merged commit d0c7534 into main Sep 14, 2026
12 checks passed
@FBumann
FBumann deleted the claude/tender-thompson-ko231z branch September 14, 2026 08:18
FBumann added a commit that referenced this pull request Sep 14, 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 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>
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 divisor under a power is unchecked on the linopy lane, and the relational refusal names no parameter

2 participants