Skip to content

CoordinateDescent 0.7 on ProximalSolvers 0.1.1 and ProximalBase 0.5 - #23

Merged
mlakolar merged 12 commits into
masterfrom
proximalopt-0.2
Oct 23, 2026
Merged

mlakolar merged 12 commits into
masterfrom
proximalopt-0.2

Conversation

@mlakolar

@mlakolar mlakolar commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Depends on ProximalSolvers 0.1.1 (mlakolar/ProximalSolvers.jl, formerly ProximalOPT). It merges into master before that is registered: the packages are developed on master until they settle, and registered then.

Until ProximalSolvers 0.1.1 is registered:

  • [sources] takes ProximalSolvers from its master (Julia 1.11 and later);
  • on Julia 1.10, which ignores [sources], CI adds it by URL.

This PR is step 5 of mlakolar/ProximalSolvers.jl#11's release order: CoordinateDescent becomes a thin layer over ProximalSolvers.

What changes

  • The estimators run on ProximalSolvers. lasso, sqrtLasso, scaledLasso(!), feasibleLasso!, LassoPath, locpolyl1 and lvocv_locpolyl1 keep their signatures. They now call ProximalSolvers' CDSolver, solve_path and reweight!, on ProximalSolvers' models.
  • The engine is gone. The passes, the screening, the Anderson state, the momentum and the group steps (coordinate_descent.jl, anderson.jl, momentum.jl, atom_iterator.jl, cd_differentiable_function.jl) are deleted, about 1,600 lines. Momentum, the default since 0.6, now runs in ProximalSolvers' CDSolver, and CDOptions(; momentum) passes through to it.
  • The solver layer stays, as wrappers.
    • coordinateDescent!, CDOptions and CDInfo map onto ProximalSolvers.solve!.
    • CDLeastSquaresLoss, CDWeightedLSLoss, CDSqrtLassoLoss and CDQuadraticLoss build ProximalSolvers models.
    • coordinateDescent! also takes any loss that ProximalSolvers.cdmodel knows, such as KronQuadratic or a scaled LogisticLoss.
  • User-defined losses keep working. A CoordinateDifferentiableFunction runs through a small adapter model, _LossModel in src/losses.jl. It calls the loss's descendCoordinate! and extends ProximalSolvers' public model interface (coordinate_step!, coordinate_scale, response_scale, loss_value).
  • scaledLasso: the square-root lasso in one run, and CDSqrtQuadraticLoss for sufficient statistics (version 0.6.1) #24 (0.6.1) is merged in and ported, in 9116d86.
  • The ProximalBase 0.5 names are used throughout: NormL1, ZeroFunction, GroupNormL2, prox_entry, prox_coordinate! and coordinates. The tests and examples pass with --depwarn=error.

Breaking changes (version 0.7.0)

  1. Penalty types. Penalties are ProximalBase 0.5 types. LassoSolution.penalty is a NormL1, whose weights are the field w (was λ).
  2. The CD*Loss constructors return ProximalSolvers models. f.r (the residuals) still works, and ProximalSolvers.intercept(f, x) replaces f.ybar - dot(f.xbar, x). CDQuadraticLoss copies an A that is not a dense Matrix; a dense one is used as it is, as in 0.6.
  3. User-defined losses.
    • The optional value is f(x) (callable), not ProximalBase.value(f, x).
    • The step should use prox_entry or prox_coordinate!.
    • On a SymmetricSparseIterate, coordinate k is coordinates(x)[k].
  4. User-defined penalties use the ProximalBase 0.5 interface: isseparable, prox_entry, and a callable value.
  5. Removed option: order = :active, which was within −27% to +17% of :random in 0.6.
  6. The iterative procedures.
    • feasibleLasso! and lvocv_locpolyl1 always warm start their inner fits. scaledLasso! has used optionsCD as it is since 0.6.1.
    • feasibleLasso!'s penalty.w holds the loadings of the last fit. In 0.6 it held the loadings recomputed after the last fit.
  7. Anderson acceleration no longer throws for an AtomIterate or for a loss without a value. It just has no effect there.
  8. LassoPath needs a decreasing λpath.
  9. CDSqrtQuadraticLoss throws DimensionMismatch for sizes that do not match (ArgumentError in 0.6.1), as CDQuadraticLoss does.

Benchmark against 0.6.0 (benchmark/compare/)

benchmark/compare/ runs two environments in interleaved processes, from the same seeds:

  • the old side, CoordinateDescent 0.6.0 from the registry on ProximalBase 0.4.1, once with its default (momentum on) and once with momentum = false;
  • the new side, this branch.

The cases are those of 0.6's momentum benchmark (designs A, E, F and locpolyl1). The table below summarizes three rounds on USC CARC Discovery (one core of an AMD EPYC 7513, Slurm job 13326700). All 19 cases are in benchmark/README.md.

case 0.7 / 0.6 default passes 0.7 / 0.6 without momentum
LassoPath (A, E, F) 0.95–1.00 identical 0.22–0.36
CDQuadraticLoss at 0.02 λmax 0.95–1.00 identical 0.22–0.39
lasso at 0.05 λmax 0.96–1.00 identical 0.46–0.79
sqrt / scaled / feasible lasso 0.94–1.01 identical 0.65–1.13
locpolyl1 1.03 — 1.11
  • The coordinate descent moved without a change, momentum included. Against 0.6.0's default, every case takes the same passes, the solutions agree to 4e-14, and the times are 0.94–1.03.
  • locpolyl1 is a 0.8 ms case.
  • These are 0.6.0's momentum cases. Before release, the comparison runs again against 0.6.1, with the cases of benchmark/scaledlasso_bench.jl added, since 0.6.1 changed scaledLasso.

Moved into ProximalSolvers for this PR

The five gaps found while porting are in ProximalSolvers now (#11):

  • random-order momentum with 0.6's default rule;
  • a check of the penalty's weights against x before the passes;
  • a public interface for models, which _LossModel now uses;
  • QuadModel using a dense Matrix{T} without copying it;
  • solve!(...; continuation) for a penalty without a level.

The NormL1/GroupNormL2 check in _solve! stays. In 0.6 only those penalties followed a path of levels with warmStart = false, and an MCP or SCAD path would change the local solution found.

The estimator-level code stays here: the λ grid, the standardization loadings, the initialization of the scaled and feasible lasso, refitLassoPath, the kernels and locpoly.

Dependents and overlapping PRs

Testing

  • Pkg.test() with --depwarn=error passes all 1077 tests on Julia 1.13.2 and 1.10.12, with ProximalSolvers 0.1.1 from its master. Aqua is included.
  • The tests of engine internals (pass iterators, caches, the screened solve) are dropped, since ProximalSolvers' suite covers those now. The momentum tests of 0.6 are back, run through CDOptions.
  • All nine examples run with --depwarn=error. Examples 08 and 09 check the custom logistic loss, elastic net and MCP against ProximalBase's built-in ones.
  • The README code blocks run.

Release order

  1. ProximalBase 0.5 is registered. Done.
  2. ProximalOPT Version 0.5: scale-free stopping rule, sparse LassoPath storage, λ grid with intercept and standardization, strong rules #11 merged, and the package is now ProximalSolvers. Its 0.1.0 registration (General #173068) merges around Oct 26, 13:22 UTC.
  3. ProximalSolvers 0.1.1 (SqrtQuadraticFunction, Examples, how to write a loss or a penalty, and the local polynomial benchmark #15) is merged; it is registered after 0.1.0.
  4. This PR merges into master, with compat ProximalSolvers = "0.1.1" and [sources] at ProximalSolvers' master.
  5. Before registering 0.7.0, on master:
    • remove [sources] (also in examples/, benchmark/ and benchmark/compare/new/) and the CI step, once ProximalSolvers 0.1.1 installs;
    • test without Manifests on Julia 1.13 and 1.10;
    • rerun benchmark/compare/ab.sh on Discovery against 0.6.1, with the scaled lasso cases, where every case must be at or below 1.0 against its default;
    • register 0.7.0, with a "## Breaking changes" section in the release notes.

🤖 Generated with Claude Code

mlakolar and others added 2 commits October 23, 2026 14:05
… 0.7.0

The estimators (lasso, sqrtLasso, scaledLasso, feasibleLasso!, LassoPath,
locpolyl1, lvocv_locpolyl1) call ProximalOPT's CDSolver, solve_path and
reweight! on ProximalOPT's models; the engine of this package (passes,
screening, Anderson acceleration, momentum, group steps) is removed.
coordinateDescent!, CDOptions and CDInfo stay as a thin layer over
ProximalOPT.solve!: the CD losses build ProximalOPT models, and a
user-defined CoordinateDifferentiableFunction runs through a model that
calls its descendCoordinate!.

ProximalBase 0.5 and ProximalOPT 0.2 are not registered yet; [sources]
takes them from their pull requests (ProximalBase #23, ProximalOPT #11).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The examples use the names of ProximalBase 0.5 (NormL1, ZeroFunction,
GroupNormL2, prox_entry) and check the custom logistic loss, elastic net
and MCP against ProximalBase's. The README and benchmark/README.md
describe the move to ProximalOPT; momentum_default_bench.jl goes, since
momentum is not in ProximalOPT yet, and order_anderson_bench.jl times the
cyclic and random orders and Anderson acceleration.

benchmark/compare/ runs 0.6.0 from the registry (with its default
momentum and with momentum = false) and this checkout on the same cases,
interleaved: against 0.6.0 without momentum every case takes the same
passes, the solutions agree to 2e-14 and the times to 3%; against the
default of 0.6.0, paths take 2.7-4.5 times as long until momentum moves to
ProximalOPT.

CI adds ProximalBase 0.5 and ProximalOPT 0.2 by URL on Julia 1.10, in one
Pkg.add call.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.10127% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.83%. Comparing base (59c778a) to head (afd9ae3).
⚠️ Report is 13 commits behind head on master.

Files with missing lines Patch % Lines
src/lasso.jl 96.38% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #23      +/-   ##
==========================================
+ Coverage   97.81%   98.83%   +1.02%     
==========================================
  Files           9        6       -3     
  Lines        1370      600     -770     
==========================================
- Hits         1340      593     -747     
+ Misses         30        7      -23     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

At λmax the group that attains it is on the boundary, and the group step
of ProximalOPT, which sums the gradient in its own order, exceeded the
test's BLAS λmax in the last bit on x86 (CI on Linux and Windows). Take
λmax (1 + √eps), as LassoPath does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
mlakolar added a commit to mlakolar/ProximalSolvers.jl that referenced this pull request Oct 23, 2026
Found while porting CoordinateDescent onto this branch
(mlakolar/HighDimensionalModels.jl#23):

- CDSolver(; momentum): Nesterov momentum on the passes over the active
  set, with adaptive restarts, as in CoordinateDescent 0.6. It is on by
  default with the random order on a SparseIterate for least squares,
  square-root least squares, quadratics and GLMs, whose caches move with
  the point. KronQuadratic takes it with momentum = true. Seeded runs take
  the passes of CoordinateDescent 0.6.0, and the passes allocate nothing.
- The interface of CDModel has public, unexported names: initialize!,
  coordinate_derivatives, update_cache!, coordinate_step!, and the
  optional coordinate_scale, response_scale, loss_value, check_penalty.
- solve! checks the weights and bounds of the penalty against the
  iterate before the passes, which read them unchecked.
- QuadModel uses a dense Matrix{T} as it is, instead of copying it.
- solve!(...; continuation) with a penalty without a level (ZeroFunction,
  IndBox) solves from zero directly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ProximalOPT's CDSolver has momentum since mlakolar/ProximalSolvers.jl#11
(b58ef35), with the default rule of 0.6, so CDOptions passes it through:

- momentum = true works again, with order = :random; the cyclic order
  refuses it, as in 0.6.
- The adapter of user-defined losses extends ProximalOPT's public
  interface (coordinate_step!, coordinate_scale, response_scale,
  loss_value) instead of its internals.
- The momentum tests, the README section, the rows of example 02 and the
  momentum column of order_anderson_bench.jl are back, without
  order = :active.
- benchmark/compare compares the passes and solutions with the default of
  0.6.0, which uses momentum.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
mlakolar and others added 2 commits October 23, 2026 16:07
ProximalBase 0.5.0 is registered (JuliaRegistries/General#173046), so its
[sources] entries go (Project.toml, benchmark/, benchmark/compare/new/,
examples/), and the Julia 1.10 CI step adds only ProximalOPT by URL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ProximalOPT's QuadModel no longer copies a dense Matrix{T}, so
CDQuadraticLoss behaves as in 0.6 there; any other matrix is copied.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Against 0.6.0's default, every one of the 19 cases takes the same passes,
the solutions agree to 4e-14 and the times are 0.94-1.03 (three
interleaved rounds on an EPYC 7513, Slurm job 13326700).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
mlakolar and others added 2 commits October 23, 2026 17:57
ProximalOPT is now ProximalSolvers (new UUID, version 0.1.0 registering
as General #173068); [deps], compat, [sources], the Julia 1.10 CI step and
the code follow. Until ProximalSolvers 0.1.1 is registered, [sources]
takes it from its branch sqrt-quadratic, which adds SqrtQuadraticFunction.

#24 ported onto ProximalSolvers:
- scaledLasso! solves the square-root lasso in one run of coordinate
  descent, on SqrtLeastSquaresLoss with NormL1(√n λ, ω), with
  options.optionsCD; σ̂ = ‖r‖/√n and the penalty is λσ̂.
- CDSqrtQuadraticLoss(A, b, c; check) is the model of ProximalSolvers'
  SqrtQuadraticFunction, which keeps #24's exact coordinate step, the
  check = false path, and momentum through a hook that recomputes the
  value after the cache moves; scaledLasso!(x, f, λ, ω, options) on it.
- #24's tests; sizes now throw DimensionMismatch, as for CDQuadraticLoss.
- benchmark/scaledlasso_bench.jl and example 04 with the 0.5 names;
  cd_bench.jl no longer calls the removed _findInitSigma!.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Its branch sqrt-quadratic is at version 0.1.1 (33a72d6). The compat in
Project.toml and the examples, benchmark and compare environments follows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
mlakolar and others added 3 commits October 23, 2026 18:15
ProximalSolvers #15 (SqrtQuadraticFunction, version 0.1.1) is merged as
541690c, so [sources] and the Julia 1.10 CI step take master instead of
the branch sqrt-quadratic, until 0.1.1 is registered.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@mlakolar mlakolar changed the title CoordinateDescent 0.7 on ProximalOPT 0.2 and ProximalBase 0.5 CoordinateDescent 0.7 on ProximalSolvers 0.1.1 and ProximalBase 0.5 Oct 23, 2026
@mlakolar
mlakolar marked this pull request as ready for review October 23, 2026 21:46
@mlakolar
mlakolar merged commit e329345 into master Oct 23, 2026
7 checks passed
@mlakolar
mlakolar deleted the proximalopt-0.2 branch October 25, 2026 07:13
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