Skip to content

fix(exact): make the rcond_free conditioning guard opt-out (#32) - #33

Merged
tschm merged 1 commit into
mainfrom
fix/exact-optional-rcond-guard
Jul 6, 2026
Merged

fix(exact): make the rcond_free conditioning guard opt-out (#32)#33
tschm merged 1 commit into
mainfrom
fix/exact-optional-rcond-guard

Conversation

@tschm

@tschm tschm commented Jul 6, 2026

Copy link
Copy Markdown
Member

Closes #32.

Problem

Exact.solve ran op.rcond_free(idx) — an O(|F|³) symmetric eigendecomposition (np.linalg.eigvalsh) — before every free-block solve. On the plain ActiveSetSolver(Exact()).solve(...) path, every outer step visits a different free set, so the 0.4.1 single-slot memo never hits and the guard fires on every step. For a dense free block it costs several times the Cholesky solve it precedes.

Measured (n=500, κ=1e4, DenseOperator): the guard's eigvalsh is ~3.6× the cholesky_solve, and a full direct solve drops from ~21 ms to ~6 ms (3.5×) with the guard off — identical solution (max|Δx| = 0).

The guard is also redundant when solve_free already fails loudly on a rank-deficient block (cvx.linalg.cholesky_solve's Cholesky→LU fallback).

Change

Add check_conditioning: bool = True on Exact:

Tests

  • New test_exact_check_conditioning_false_skips_rcond_guard: asserts rcond_free is called 0 times when disabled, the solve still converges to the planted optimum, and the result matches the guarded solve bit-for-bit.
  • Full suite green (86 passed), coverage gate satisfied, ruff clean.

🤖 Generated with Claude Code

The Exact inner solver ran op.rcond_free() -- an O(|F|^3) symmetric
eigendecomposition -- before every free-block solve. On the plain
solve() path each outer step visits a different free set, so the
0.4.1 memo never hits and the guard fires every step, where it can
cost several times the Cholesky solve it precedes (~3.6x on a dense
250-dim free block; a 500-var direct solve drops from ~21ms to ~6ms).

The guard is also redundant when solve_free already fails loudly on a
rank-deficient block (cvx.linalg.cholesky_solve's Cholesky->LU path).

Add check_conditioning: bool = True on Exact; default keeps current
behavior, check_conditioning=False skips the eigendecomposition and
lets solve_free surface any singularity itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 6, 2026 18:34

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 makes the expensive rcond_free conditioning guard in nncg.inner.Exact optional via a new check_conditioning flag, enabling callers to skip an O(|F|^3) eigendecomposition when they prefer raw solve_free performance.

Changes:

  • Add Exact(check_conditioning: bool = True) and gate the conditioning check on this flag.
  • Expand Exact’s docstring to explain the performance motivation and how to opt out.
  • Add a regression test ensuring rcond_free is never called when check_conditioning=False and that solutions match the guarded path.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/nncg/inner.py Adds check_conditioning flag and conditionally skips the rcond_free guard; updates documentation accordingly.
tests/test_operators.py Adds a test that counts rcond_free calls and verifies correctness when conditioning checks are disabled.

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

Comment thread src/nncg/inner.py
Comment on lines +239 to +243
can cost several times the Cholesky solve it precedes. The guard is also
redundant when ``solve_free`` already fails loudly on a rank-deficient block
(e.g. ``cvx.linalg.cholesky_solve``'s Cholesky→LU fallback). Set
``check_conditioning=False`` to skip it and let ``solve_free`` surface any
singularity itself.
@tschm
tschm merged commit afa2631 into main Jul 6, 2026
56 checks passed
@tschm
tschm deleted the fix/exact-optional-rcond-guard branch July 7, 2026 02:01
@tschm
tschm restored the fix/exact-optional-rcond-guard branch July 13, 2026 20:34
@tschm
tschm deleted the fix/exact-optional-rcond-guard branch July 14, 2026 09:45
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.

Exact inner solver: unconditional rcond_free eigendecomposition dominates the direct-solve cost (~3.6x the solve it guards)

2 participants