Repository navigation
perf(engine): an update that gives a linear constraint's coefficients new numbers at the coordinates they had skips most of the build - #2071
Conversation
… new numbers at the coordinates they had skips most of the build Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA4oeSor7jjZ4vwux1G
…erf/update-coefficients # Conflicts: # src/specsolve/_engine/assembly.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA4oeSor7jjZ4vwux1G
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
Documentation build overview
110 files changed ·
|
Merging this PR will regress 1 benchmark1 warning
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
…erf/update-coefficients
Note
The following content was generated by AI.
An update that changes a linear constraint's coefficients, at the coordinates they had, now keeps the labels and re-reads only the matrix entries of the constraints it feeds. On top of #2070, such an update takes 0.25–0.49× the time. The solver still loads again.
What changed
#2070 counted a parameter that scales a variable as structural, so any such update built in full. Over the ports, those were 67 of the 87 single-parameter updates that missed #2070's path.
_structural_parameterskeepswhere,cases:conditions and shift or window amounts. A factor or divisor of a variable is structural now only in a quadratic constraint, whose rows a sink takes whole, coefficients included._coefficient_parameters(c)is the walk perf(engine): an update that gives a bound, a right-hand side or a cost new numbers at the coordinates it had skips most of the build #2070 had for products and quotients, now per constraint.refilled: for each constraint whose coefficients read a changed name,_linear_shareplaces its linear terms on the stored rows, and_resplicedputs them into the CSR matrix in place of that constraint's rows. A coefficient that falls to zero takes its entry with it, so the rows after it move by the difference inrow_starts. A constraint here built every row it labelled (perf(engine): an update that gives a bound, a right-hand side or a cost new numbers at the coordinates it had skips most of the build #2070's dropped-row condition), so one with nowherespans the whole product of its dims and is placed densely, as in a build. The coefficient range and the nonzero count are read again, and the ranges keep build order._linear_shareis the term placement that was inline in_build_constraint, so the build and the refill share one copy.The held solver is untouched. A changed coefficient fails its
keeps()test, so the next solve loads the model again. Taking changed coefficients in place (HiGHSchangeCoeff, GurobichgCoeffs) is the next PR.Docs:
docs/howto/faster.mdand the labels paragraph ofdocs/development/architecture.mdname coefficients and say the solver still loads again.Probes and reach
tests/test_update.py:test_a_change_of_numbers_alone_hands_over_what_a_fresh_build_does(from perf(engine): an update that gives a bound, a right-hand side or a cost new numbers at the coordinates it had skips most of the build #2070): every real-valued parameter of every port buttsp_mtz, ×1.25 + 0.5, hand-off anddiagnostics()equal to a fresh build's. 240 of 264 such updates over 42 ports now take the fast path, against 177 on perf(engine): an update that gives a bound, a right-hand side or a cost new numbers at the coordinates it had skips most of the build #2070.test_new_numbers_are_read_again_on_the_old_labels_only_where_nothing_else_can_move: a coefficient, a divisor, and a coefficient falling to zero in front of a second constraint now take the path. Each case also says how many times the solver loads: 2 for a coefficient, 1 for a bound, a cost or a constant side.test_a_coefficient_of_a_quadratic_constraint_builds_in_full.test_a_divisor_falling_to_zero_is_refused_as_a_fresh_build_refuses_it: sameDataError, same words.Outside the suite: storage xs
etaand network setatake the path, and their hand-off and diagnostics equal a fresh build's.Mutation table
Each guard broken by one text substitution, the full suite run (
pytest -q -n 4), the file restored withgit checkout --,__pycache__dropped on both sides, the tree clean after each. The first six at6db4b14, the last three at8b6ac48.where6db4b14; caught, 26 failed, by the diagnostics comparison added for it6db4b14; caught, 11 failed, the same wayThe diagnostics comparison also found that #2070 listed a re-read bound or right-hand-side range at the end of its frame. That is fixed on #2070 (
c70193d) and merged here.A/B of the update, this container (4 cores, not the publishing machine)
Base #2070's head
c70193d, head8b6ac48. One fresh process per measurement, base and head alternated ABBA for three rounds. Each process builds once, then times 12 calls ofmodel.update({param: …}), each with the values ×0.9–1.1 at the same coordinates, and reports the median; the table gives the median over the three processes. Only the update is timed: no solve, so the solver's reload is not in these numbers. Started once the load average fell below 1.5: 1.36 before, 1.31 after.etaetaetaetaload, control: on #2070's path either wayPer-process medians, ms: storage xs
etabase 39.5 / 35.3 / 36.4, head 18.5 / 17.9 / 16.2; storage s base 47.2 / 55.1 / 52.2, head 21.6 / 25.9 / 25.6; network s base 104.8 / 113.9 / 98.2, head 37.6 / 35.1 / 38.4; network m base 372.0 / 352.4 / 336.3, head 85.0 / 93.4 / 86.9; control base 9.8 / 11.3 / 11.2, head 11.1 / 11.1 / 11.0.Gates at 8b6ac48, on mathspec 0.4.0
No pixi in this container, so each gate ran in a uv environment synced from #2065's
uv.lock(--group dev --group docs --all-extras, Python 3.12, polars 2.0.0):ruff check .,ruff format --check .(0.16.9): clean.pyrefly checkon the package (1.3.1): 0 errors.pytest -q -n 4: 5945 passed, 459 skipped, 4 xfailed.pytest tests/test_docs_math.py tests/test_docstring_links.py(docs-test): 116 passed at6db4b14.Not run:
docs-build, the floors environment,test-via-pyarrow.Not done. The solver still loads again on a changed coefficient, which is the next PR. Two smaller cases still build in full: a
wherewhose answer does not change (11 port updates) and a constraint that dropped a row (9). Stacked on #2070.🤖 Generated with Claude Code
https://claude.ai/code/session_018B3nA4oeSor7jjZ4vwux1G