Skip to content

test(solver): assert the max_outer contract instead of a magic outer count - #49

Merged
tschm merged 1 commit into
mainfrom
refactor/decouple-counter-tests-47
Jul 9, 2026
Merged

test(solver): assert the max_outer contract instead of a magic outer count#49
tschm merged 1 commit into
mainfrom
refactor/decouple-counter-tests-47

Conversation

@tschm

@tschm tschm commented Jul 9, 2026

Copy link
Copy Markdown
Member

Addresses #47.

Audit first

I audited every iteration-count / trajectory assertion in the two files the issue names. Most were already compliant:

What changed

The one genuinely brittle pattern was the two cap tests hard-coding res.outer == 1 for max_outer=1 — a magic expected count. Each is now a contract test:

full = ActiveSetSolver(inner=CG()).solve(op, b).outer   # uncapped, measured not hard-coded
assert full > 1
for cap in range(1, full):                              # every cap short of convergence
    res = ...solve(..., config=ActiveSetConfig(max_outer=cap))
    assert not res.converged
    assert res.outer == cap                             # stops at exactly the cap

This is strictly stronger than the original (covers every cap below convergence, not just 1), reads only the public Result surface, and is robust to backend-dependent iteration-count drift because the reference count is measured, not pinned.

Verification

Check Result
make test 102 passed, 100% coverage
make fmt all hooks pass

Acceptance criterion (#47): iteration-count assertions reference only the public Result surface and express contracts/bounds rather than exact internal state; the paper's claims are still enforced; suite green at 100% coverage.

🤖 Generated with Claude Code

…47)

The two cap tests hard-coded `res.outer == 1` for `max_outer=1`. Replace
each with a contract test that measures the uncapped outer count from the
public `Result` and asserts the loop stops at exactly `cap` steps for
every cap short of convergence. Stronger (covers all caps, not one),
reads only the public surface, and robust to backend-dependent
iteration-count drift.

The remaining count assertions are left as-is by design: `.inner` checks
are already relative/bounded, the warm-start `outer == 1` are documented
single-outer-step guarantees, the trajectory equalities compare two
solvers (inexactness lemma), and the _CountingOperator call-counts are
justified white-box regressions for #30/#32 (call patterns not
observable through Result). tests/test_paper/test_results.py was already
fully behavioural and is unchanged.

102 tests pass at 100% coverage.

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

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 updates the solver test suite to avoid brittle, hard-coded outer-iteration counts by asserting the public Result.outer / Result.converged max_outer contract instead of expecting a specific outer == 1 outcome for max_outer=1.

Changes:

  • Reworked the bound-constrained max_outer cap test to measure the uncapped outer count and verify truncation behavior for every smaller cap.
  • Reworked the equality-constrained max_outer cap test in the same way.
  • Expanded docstrings to explain why the new assertions are more robust to backend-dependent iteration drift.

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

Comment on lines +172 to +174
full = ActiveSetSolver(inner=CG()).solve(op, b).outer # uncapped step count
assert full > 1 # a non-trivial support needs more than one step to reach
for cap in range(1, full):
Comment on lines +578 to +580
full = ActiveSetSolver(inner=CG()).solve_eq(op, b, b_eq, c_eq).outer # uncapped step count
assert full > 1
for cap in range(1, full):
@tschm
tschm merged commit 0950ca9 into main Jul 9, 2026
65 checks passed
@tschm
tschm deleted the refactor/decouple-counter-tests-47 branch July 9, 2026 14:55
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