aa: opt-in trust region + adaptive regularization#54
Open
bodono wants to merge 6 commits into
Open
Conversation
The type-II regularizer was ε · ||Y||_F², while type-I used ε · ||S||_F · ||Y||_F. On slow-contraction maps (typical of ADMM/DRS applications), the type-II scaling decays quadratically as ‖Y‖ → 0 and underflows to noise (~1e-15) near the optimum. The augmented LS solve then produces huge γ values, and the safeguard's monotone check (‖g_new‖ ≤ ‖g_old‖) is too weak to catch the resulting "creep" — each step marginally reduces the residual without approaching the fixed point. Switching type-II to the same ||S||_F · ||Y||_F formula keeps r in a useful range without changing behavior on cases where ||S|| ≈ ||Y|| (gradient descent on convex quadratics, etc.). Diagnosed on the SCS Maros-Meszaros / netlib benchmark where ~30 problems previously hit max_iters with type-II at default settings; this change is a meaningful component of the recovery on those. All 34 existing tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ust_factor accept INFINITY)
Adds a single new aa_init parameter, `trust_factor`, that turns on two
coupled mechanisms for slow-contraction maps (ADMM/DRS):
1. Trust region: each solve rejects the step if ||D γ||_2 exceeds
trust_factor * ||g||_2. Catches the failure mode where γ passes the
LS solve and the weight-norm cap but the resulting iterate
displacement is much larger than the current residual — the LS
basis is pointing away from the descent direction. Each step
marginally satisfies the safeguard's monotone test while the
iterate drifts laterally, accumulating to no real progress.
2. Adaptive regularization: replaces the ε·||S||_F·||Y||_F baseline
with a self-tuning r. Starts at 1.0 (so γ ≈ 0 and AA ≈ DRS),
shrinks by 0.9× on each safeguard accept (let AA do more), grows
by 10× on any rejection (trust, safeguard, or in-solve). This is
what keeps LASER-style problems converging even when the trust
region trips occasionally — each trip damps subsequent γ.
The two mechanisms feed each other: trust region detects bad steps,
the adaptive r bumps the regularization in response, the next solve
produces smaller γ and the trust check usually passes. Equilibrium
settles at the r value appropriate for the problem.
trust_factor = INFINITY (the new "no cap" sentinel) disables both
mechanisms and keeps the original ε·||S||·||Y|| path, so existing
callers see no behavior change. Same convention now applies to
max_weight_norm: INFINITY is accepted (was previously rejected by an
isfinite() check).
Diagnosed and validated on the SCS Maros-Meszaros / Netlib QP/LP
benchmark, where ~30 problems previously hit max_iters with type-II
at default settings. With trust_factor=10, both types now solve 30/33
of those problems cleanly, with no regression on either working set
(no problem that previously solved becomes inaccurate).
All 34 existing aa tests pass — they implicitly opt out by passing
INFINITY for the new parameter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
max_weight_norm = inf is now accepted (was rejected by the old isfinite() check), and trust_factor follows the same convention. NaN and non-positive values remain rejected with an updated error message. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a parenthetical to the `regularization` row in the parameters table calling out that Type-I and Type-II now share the same scaling formula (the original Type-II `‖Y‖²` underflowed on slow-contraction maps). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add trust_factor to the parameters table. - Add a new "Trust-region mode (opt-in)" section explaining the mechanism, when it helps (ADMM/DRS slow-contraction maps), and when to leave it off (the default). - Update C / Python signatures to show the new parameter. - Update max_weight_norm row to note INFINITY is now an accepted "no cap" sentinel. - Expand the stats / diagnostics tip to point at trust_factor as a remedy for the high-n_safeguard_reject + low-last_regularization + high-last_aa_norm pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a single new
aa_initparameter,trust_factor, that turns on two coupled mechanisms for slow-contraction maps (ADMM/DRS).trust_factor = INFINITY(the new "no cap" sentinel) is the default and keeps the existing behavior — no test changes for callers who don't opt in.When
trust_factoris finite and positive (recommended value: ~10):Trust region — each solve rejects the step if
||D γ||_2 > trust_factor · ||g||_2. This catches the failure mode where γ passes the LS solve and weight-norm cap, but the resulting iterate displacement is much larger than the current residual — i.e. the LS basis is pointing somewhere unhelpful. Each step marginally satisfies the safeguard's monotone test, but the iterate drifts laterally rather than approaching the fixed point.Adaptive regularization — replaces
ε·||S||_F·||Y||_Fwith a self-tuningr:1.0(so initial γ ≈ 0, i.e. AA ≈ DRS).r *= 0.9(earn trust, let AA do more).r *= 10(back off toward DRS).[1e-12, 1e30].The two mechanisms feed each other: the trust region detects bad steps, adaptive
rdamps γ in response, the next solve produces smaller γ and the trust check usually passes. The equilibriumrsettles at the value appropriate for the problem.Same INFINITY-as-sentinel convention now applies to
max_weight_normas well — previously anisfinite()check rejected it; the new validation usesisnan() || ≤ 0, so callers can writeINFINITYfor both fields to mean "no cap." The 34 existing tests pass through identically because they don't opt in.Why this exists
Diagnosed on the SCS Maros-Meszaros / Netlib QP/LP benchmark. With the best AA settings at default
aa_initparameters, ~30 problems hitmax_itersfor type-II while type-I solved them. The mechanism:||Y||_Fand||S||_Fshrink andr = ε·||S||·||Y||underflows to ~1e-15.γblows up from the resulting LS solve (~||γ||₂ = 700observed on PRIMALC1).||g_new|| ≤ ||g_old||is too weak to catch this — a wild step can satisfy it by a hair while the iterate drifts laterally.The trust region directly catches the "step too big" condition, and adaptive
rprovides the feedback so AA self-tunes per-problem rather than picking a magic regularization at init time.Empirical results (SCS benchmark)
On the type-II failure set (33 problems where type-II hits
max_itersat default settings, while type-I solves):Both types now solve essentially all the problems the other can solve, at parity. The 3 still-failing problems (
stair,capri,share2b) also hitmax_iterswith plain DRS — they're DRS-level failures, not AA failures.Test plan
make testpasses all 34 existing teststrust_factor(NaN/0/negative rejected; INFINITY and positive finite values accepted)max_weight_norm=INFINITYnow accepted (previously rejected — see flipped assertion intest_nonfinite_scalar_options_rejected)Breaking changes
aa_initsignature now takestrust_factorbetweenmax_weight_normandir_max_steps. Callers in this repo (tests, gd, bench, Python binding) all updated to passINFINITY(= old behavior). Downstream callers (e.g. SCS) need a one-line update to passINFINITYfor the new parameter, or a finite positive value to opt in.🤖 Generated with Claude Code