Skip to content

Stop the graphical Lasso when it cycles instead of converging - #179

Merged
jchiquet merged 1 commit into
masterfrom
glasso-stagnation
Sep 23, 2026
Merged

jchiquet merged 1 commit into
masterfrom
glasso-stagnation

Conversation

@jchiquet

Copy link
Copy Markdown
Member

Context

Running PLNnetwork() on oaks raises convergence warnings from the graphical Lasso at low penalties, and earlier so when the tree covariate is added. This is not a regression from the internal solver: glassoFast behaves identically on these problems (same 10001 sweeps, 1.6e-16 difference) -- it simply never reported it.

Diagnosis

It is not slow convergence but a limit cycle. The convergence criterion plateaus (4.0e-5, 4.6e-5, 4.9e-5, 2.6e-5, ...) instead of decaying; 200000 sweeps do not converge either. Loosening thr is the wrong lever: 1e-3 and 1e-2 both still fail, and 1e-2 changes the support.

The cause is in the model, not the solver: on oaks, p = 114 and n = 116, so with tree the residual degrees of freedom are 113 < p and the residual covariance is singular, positive definite only through the S² ridge.

Change

The solve stops after stall_patience = 1000 consecutive sweeps without improving its best criterion, and reports status = "stalled". The patience is calibrated on a census of 246 penalized problems (Gaussian and count data, p from 10 to 120, full penalty paths): converging problems never went more than 373 sweeps without progress, cycling ones went 4000+.

Stopping early costs nothing, because grinding does not buy accuracy -- the iterates wander inside the cycle. Against a 50000-sweep reference on oaks: the old 10000-sweep answer is 3.00e-04 away with a different support; the new 1138-sweep answer is 6.98e-04 away with an identical support.

A stalled solve no longer warns: it says something about the problem (too weak a penalty for a nearly rank-deficient covariance), not about the solution. Warnings remain for the genuine numerical failures.

Measurements

Optimized build, OMP_NUM_THREADS=2, same machine, master vs this branch:

oaks master this branch
Abundance ~ 1 14.4 s 5.6 s
Abundance ~ 0 + tree 126.0 s 18.2 s

Selections are identical on both sides: BIC 0.5561 / EBIC 0.5561 without covariate, BIC 0.4127 / EBIC 0.5670 with tree.

Checks

  • full test suite: 1710 pass, 0 fail, 0 warning (NOT_CRAN=true, nothing skipped)
  • R CMD check --as-cran: 0 errors, 0 warnings, 1 NOTE (a local compilation flag, not from the package)

API

graphical_lasso() gains status, delta, and the trace / stall_patience arguments; fits record glasso_stalled next to glasso_nonconverged in their monitoring.

🤖 Generated with Claude Code

On an ill-conditioned covariance -- typically rank-deficient, as PLNnetwork()
produces when the number of species approaches the number of samples -- the
block coordinate descent settles into a small limit cycle: the convergence
criterion stops decreasing and oscillates just above its threshold forever,
while the solution itself no longer moves. glassoFast spends its whole
10000-sweep budget on these and reports success regardless.

The cycle is now detected after 1000 consecutive sweeps without improving the
best criterion seen so far, and reported as status = "stalled". The patience
comes from a census of 246 penalized problems (Gaussian and count data, p from
10 to 120, along full penalty paths): problems that do converge never went more
than 373 sweeps without progress, while cycling ones went 4000+.

Stopping early costs nothing, because sweeping on does not buy accuracy: the
iterates wander inside the cycle rather than settle. On oaks, the solve stops
after ~1100 sweeps instead of 10000, and both that answer and the 10000-sweep
one sit within 1e-3 of a 50000-sweep grind, with the same number of edges.
PLNnetwork() on oaks goes from 14.4s to 5.6s without covariate and from 126.0s
to 18.2s with the tree covariate, with identical BIC and EBIC selections.

The solver gains a `status` ("converged", "stalled", "max_iter",
"inner_failure", "degenerate"), a `delta` (best criterion relative to its
threshold), and optional `trace` and `stall_patience` arguments. Fits record
`glasso_stalled` alongside `glasso_nonconverged` in their monitoring, and warn
only for the numerical failures: a stalled solve says something about the
problem -- usually too weak a penalty for a nearly rank-deficient covariance --
rather than about the solution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jchiquet
jchiquet merged commit e700318 into master Sep 23, 2026
9 checks passed
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.

1 participant