Skip to content

Hoist the free-set restriction out of the inner CG loop (closes #12) - #13

Merged
tschm merged 2 commits into
mainfrom
perf/hoist-free-set-restriction
Jul 3, 2026
Merged

Hoist the free-set restriction out of the inner CG loop (closes #12)#13
tschm merged 2 commits into
mainfrom
perf/hoist-free-set-restriction

Conversation

@tschm

@tschm tschm commented Jul 3, 2026

Copy link
Copy Markdown
Member

`_free_matvec` now builds the pre-sliced free-block operator once per working set via `restricted(idx)` (Jebel-Quant/linalg#89) and returns its plain `matvec`; the old `apply_free`-per-iteration path remains as a fallback for cvx-linalg releases without `restricted` and for backends that inherit its raising default.

Effect: identical iteration counts and certificates, ~5-15x less per-iteration wall clock (the same anti-pattern as fast_minimum_variance#72/#73). On the minvar paper's balance study (S&P 500, n=494, sleeves p in {1,4,8}) the solve times drop accordingly; tables regenerate with unchanged iteration columns.

Tests: hoisted path returns the restricted matvec, fallback works for legacy operators (no `restricted`) and for the raising default. Suite passes against both the released cvx-linalg and linalg#89.

🤖 Generated with Claude Code

_free_matvec returned lambda v: op.apply_free(idx, v), so every inner
CG iteration re-gathered the operator's storage (two fancy-indexed
copies of the Gram factor per matvec): identical iteration counts,
roughly 5-15x per-iteration wall clock. It now builds the pre-sliced
free-block operator once via restricted(idx) (cvx-linalg >= 0.10,
Jebel-Quant/linalg#89) and returns its plain matvec, falling back to
apply_free for older releases or backends without a pre-sliced form.

Same root cause as fast_minimum_variance#72 (fixed there in #73).
Closes #12.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 15:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the inner CG matvec factory by hoisting free-set restriction construction out of the per-iteration path when the backend supports restricted(idx), while retaining a legacy fallback via apply_free.

Changes:

  • Update _free_matvec to prefer op.restricted(idx).matvec and fall back on apply_free when unavailable or unimplemented.
  • Add a new test module covering both the hoisted path and the fallback behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/nncg/solver.py Hoists free-set restriction via restricted(idx) when supported; preserves apply_free fallback.
tests/test_free_matvec.py Adds coverage for restricted-capable operators and fallback scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_free_matvec.py Outdated
Comment on lines +30 to +31
op, a = _dense_pair(1)
idx = np.array([1, 3, 4])
Comment thread tests/test_free_matvec.py Outdated
Comment on lines +46 to +47
op, a = _dense_pair(2)
idx = np.array([0, 4])
Comment thread src/nncg/solver.py
Comment on lines +98 to +103
restricted = getattr(op, "restricted", None)
if restricted is not None:
try:
return restricted(idx).matvec
except NotImplementedError:
pass # backend without a pre-sliced form; fall back below
- cast restricted(idx).matvec through MatVec so mypy stops flagging the
  getattr-sourced Any as a no-any-return
- add the missing _dense_pair docstring (docs-coverage gate is 100%)
- adopt ruff's fixes in test_free_matvec (unused op -> _op, drop stale noqa)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tschm
tschm merged commit fba9cd0 into main Jul 3, 2026
55 checks passed
@tschm
tschm deleted the perf/hoist-free-set-restriction branch July 4, 2026 03:02
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