Skip to content

feat(data): data that fails what a file assumes of it is refused at the door - #1705

Closed
FBumann wants to merge 1 commit into
mainfrom
claude/lpspec-589-592-update-qa51o2
Closed

FBumann wants to merge 1 commit into
mainfrom
claude/lpspec-589-592-update-qa51o2

Conversation

@FBumann

@FBumann FBumann commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Prompt: "update lpspec to 589 and another stacked pr to 592" — "Maybe we can use one of the existing draft PRs on lpspec?"

Note

The following content was generated by AI.

The pin follows math-spec #589. An assumptions: entry is evaluated at the door and the data refused in the language's own words, and a where may compare arithmetic over parameters on both lanes — #589's history carries #566, so one pin asks for both.

Stacked on this: #1706 (math-spec #592, count and shift over a predicate).

What this changes

assumptions.py, the file's own entries. validate_assumptions runs from tidy_sources once every parameter, dimension and lookup is read, so both lanes enter through the one door. Each entry's predicate is evaluated over the product of the dims its two masks name, cut by its where, through the relational lane's mask compiler — the one reader of a mask this package has that needs no model built first. The failing coordinates are the admitted product less the rows the predicate keeps, rather than the rows it drops: a mask certain of a parameter inner-joins, so a coordinate with no row is gone from the carrier before the predicate is read, and that is exactly the coordinate a missing row has to count as failing. The sentence is assumption_message's, with the count and the first five coordinates appended.

curves.py reads the block's conditions from Program.assumptions. PiecewiseDeclaration.checks and check_message are gone upstream. _one finds the condition of a kind that a block's method implies, and _refusal words it under the name it is filed as — which is the name a reader greps for, cost_curve increasing.

Arithmetic over parameters in a where, on both lanes. A side compiles to constant fragments alone and is added over the comparison's dims the way a constant side of a constraint is; a coordinate no fragment reaches stays null, so the comparison reads false there as every other atom over a missing value does. The eager lane evaluates each side as a constant position and compares element-wise. Both handlers reach their lane's expression evaluator through the one lazy import the grammar forces — a mask reads an expression and a cases expression reads a mask — so DELIBERATE_LAZY_IMPORTS is no longer empty and hard rule 0 in ARCHITECTURE says why.

ArithmeticComparison is in program.Predicate and no lowered mask holds one, so it is excused from both lanes' census under NEVER_LOWERED, and test_no_never_lowered_node_survives_lowering checks that claim about upstream rather than trusting it.

Which draft PRs this reuses, and why they are not the vehicle

The code comes from the two existing drafts; neither branch could carry it.

So both drafts are superseded rather than built on. Closing them is yours to decide — this PR does not touch them.

Gates

pixi is refused by this environment's egress proxy, so the gates ran from a uv environment on Python 3.12 (uv sync --all-extras --group dev). That is a departure from the "prefix every command with pixi run" default, and the only one taken deliberately. The work was done in the primary checkout rather than a worktree, for the same reason: one solved environment per session.

gate result
pytest -q -n auto 4026 passed, 320 skipped, 1 xfailed (main: 4013 passed)
ruff check . clean
ruff format --check . 329 files already formatted
pyrefly check 0 errors, both configs
python -m tools.gallery_math 47 pages refreshed, 3 changed
docs-build, test-bench, sweep not run — no docs toolchain in this environment, and nothing here touches bench/

The 320 skips are this environment's: no typst binary, and the solver-licence cases.

Mutation table

python -m tools.mutate, each guard deleted in turn, the suite run, the file restored and the tree checked clean.

mutation result
the assumptions check at the door (sources.py:108) caught
the refusal when a coordinate fails (assumptions.py:68-69) caught
the variable-free guard on a where side (predicates.py:223-225) caught

Tree clean after the run.

The third row is the guard the suite would otherwise survive deleting, because lowering refuses a variable on a where side first. test_a_where_side_holding_a_variable_is_caught_rather_than_compiled_as_a_constant is the purpose-built probe that reaches it, which is why the row measures it from the compiler rather than from a model.

Coverage moved, and what was deliberately left out
  • test_both_lanes_refuse_the_same_where[two-parameters-compared] is gone: p_max > cost is now admitted, so it moves to ACCEPTED in the same file, next to 0.5 * p_max > 0, sum(p_max, over=generator) > 0 and 1 > 0.
  • ArithmeticComparison goes in COVERED_ELSEWHERE, pointing at the never-lowered probe, so the parity sweep's census still names a test for every resolved predicate.
  • The three piecewise gallery pages gain their generated Assumptions section; docs/reference/data.md gains the refusal row and docs/about/linopy.md the lane's reading of a comparison of expressions.
  • Not done, on purpose: nothing emits an assumptions: entry from a piecewise: block — that is math-spec A modeller should not have to shrink the model to solve it #582's, on top of refactor: one answer per question #589. The pin is a commit rather than a tag because refactor: one answer per question #589 is unmerged, which is also why this PR is a draft: it cannot merge before refactor: one answer per question #589 does. uv.lock moves only the math-spec entry.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TWu2WSipjg5T3khNLfTXDK

…he door

The pin follows math-spec #589, whose history carries #566, so this lands
both halves the new pin asks for: an `assumptions:` entry is evaluated once
every parameter is read, and a `where` may compare arithmetic over parameters
on either lane.

`PiecewiseDeclaration.checks` and `check_message` are gone upstream — a
block's conditions stand in `Program.assumptions` beside the file's own
entries — so `curves.py` reads them from there under the name a refusal
quotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWu2WSipjg5T3khNLfTXDK
@read-the-docs-community

Copy link
Copy Markdown

@codspeed

codspeed Bot commented Sep 21, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 58 skipped benchmarks1


Comparing claude/lpspec-589-592-update-qa51o2 (345b8c6) with main (e92827f)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 (fb725bd) during the generation of this report, so e92827f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

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.

2 participants