Skip to content

perf(engine): a constraint whose where requires a parameter no longer checks that parameter covers its rows - #2035

Draft
FBumann wants to merge 4 commits into
mainfrom
perf/skip-covered-constant-check
Draft

FBumann wants to merge 4 commits into
mainfrom
perf/skip-covered-constant-check

Conversation

@FBumann

@FBumann FBumann commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Prompt: What about specsolve could we speed up?

Note

The following content was generated by AI.

A constraint like sector's balance (where: demand, == demand) no longer checks that demand covers its rows: the mask already kept only rows where demand has a value. The sector build at its largest size is about 11% faster.

Why the check cannot fail there

refuse_short_constants anti-joins each sparse constant-side parameter against the rows the constraint builds. It is now skipped for a parameter the constraint's where requires on its AND spine, that is, certain_names(c.where). A name the where reads only under an or or a not is still checked. Two facts make the skip safe:

  • Every row passed the mask, and the mask reads that parameter at the row's own coordinates. ParameterDefined needs a non-null, finite value there, and ParameterComparison is false on a null.
  • The loader refuses a where that reads a parameter with a dim outside the constraint's dims (where-parameter 'c' reads dims ['k'] outside the frame ['t']). The check keys on the parameter's dims that the constraint also has, so every key it would ask for is the coordinate of a row the mask kept. This holds whether or not the constant side then sums one of those dims away.

So the anti-join can only ever find zero missing coordinates for such a parameter. A parameter under a shift is unchanged: the old check also anti-joined the unshifted keys, and constant_side still finds a shifted gap as a null on the rows pass.

_certain_names in predicates.py becomes certain_names, because coverage.py now reads it.

Probe, mutation table and gates

New tests/test_absence.py::test_the_coverage_check_skips_a_parameter_the_mask_requires records which parameters the check asks for:

case skip in place skip removed (coverage.py from main)
where: c pass fail: asks c
where: c > 0 pass fail: asks c
where: w > 0 (c still asked) pass pass
where: c or w > 0 (c still asked) pass pass

The or case was added after review (d1d5ed1). Without it, widening the skip to every name the where reads left the full suite green, while where: 'c or w > 0' over a short c was then accepted with no error instead of refused.

Mutations of required = certain_names(c.where) in refuse_short_constants, by hand (git checkout -- to restore, __pycache__ dropped on both sides, tree clean after each), on tests/test_absence.py:

mutation result
skip removed (frozenset()) caught, 2 failed
skip every name the where reads (c.where.names_read) caught, 1 failed (c only under an or)

The existing tests that refuse a short constant side, including test_a_where_is_the_escape_from_the_constant_side_check, still pass.

Gates at d1d5ed1: pixi run check 5605 passed, 566 skipped, 4 xfailed.

A/B, build only (`sps.build`, no solver), this container (4 cores, not the publishing machine)

Base 6e797ad, head this branch before the merge of main. One process per measurement, alternated base/head three times. Each process times sps.build 5 times (9 for sector m). Numbers are min / median per process, in seconds.

model base head
sector l (3M variables) 2.08 / 2.26, 1.93 / 2.06, 1.87 / 1.93 1.82 / 1.90, 1.73 / 1.94, 1.66 / 1.79
sector m (300k) 0.210 / 0.225, 0.200 / 0.224, 0.212 / 0.229 0.188 / 0.208, 0.211 / 0.240, 0.201 / 0.247
nodal l (control, no where) 0.474 / 0.513, 0.502 / 0.558, 0.486 / 0.515 0.443 / 0.503, 0.472 / 0.537, 0.474 / 0.520

At sector m the difference is within this machine's noise. nodal has no where on its balance and does not change. Since then, the branch has added a test only, and no src/ change landed on main between 6e797ad and 2588883.

Not done here: the right-hand side of the same constraint still joins demand onto the rows a second time. That is #2036.

🤖 Generated with Claude Code

https://claude.ai/code/session_011o3uXC8ofHZk34Wpb3uFtR
https://claude.ai/code/session_015LJh8zKF2v86hQ9rhfka1q

… checks that parameter covers its rows

The coverage check anti-joins a sparse constant-side parameter against the
rows a constraint builds. Where the constraint's where requires that
parameter on its AND spine, the mask kept only rows where it has a value,
and the loader refuses a where that reads a dim outside the constraint's,
so the anti-join can find nothing. It is skipped there.

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

read-the-docs-community Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 specsolve | 🛠️ Build #35142210 | 📁 Comparing d1d5ed1 against latest (6e797ad)

  🔍 Preview build  

2 files changed
± about/benchmarks/index.html
± about/changelog/index.html

@codspeed

codspeed Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 104 untouched benchmarks
⏩ 307 skipped benchmarks1


Comparing perf/skip-covered-constant-check (d1d5ed1) with main (2588883)

Open in CodSpeed

Footnotes

  1. 307 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. ↩

@FBumann FBumann added the perf Wall time, memory, or I/O cost — not a wrong answer label Nov 11, 2026 — with Claude
@FBumann
FBumann marked this pull request as draft November 11, 2026 10:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

perf Wall time, memory, or I/O cost — not a wrong answer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants