Skip to content

Release v0.7.0: optional LinearSolve.jl backend, rename Mem to F1Cache - #20

Merged
briochemc merged 1 commit into
masterfrom
bp-claude/linearsolve
May 13, 2026
Merged

briochemc merged 1 commit into
masterfrom
bp-claude/linearsolve

Conversation

@briochemc

@briochemc briochemc commented May 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • v0.7.0 release: optional LinearSolve.jl backend, exposed via a new linsolve kwarg on the renamed F1Cache constructor. Default behaviour (linsolve = nothing) is unchanged from 0.6.x.
  • Breaking renames with deprecation wrappers retained for 0.7.x: Mem -> F1Cache, initialize_mem(...) -> F1Cache(...), update_mem! -> update_cache!, Mem.A field -> F1Cache.linear_cache.
  • Tests refactored into a symmetric for linsolve in (nothing, ...) pattern for dense (test/dense_rosenbrock.jl, new) and sparse (test/sparse_aibecs.jl, renamed from AIBECS_test.jl). Redundant test/rosenbrock.jl deleted (its assertions are now exercised by the linsolve = nothing arm of dense_rosenbrock.jl).

What ships in the extension

The new F1MethodLinearSolveExt extension (loaded when LinearSolve is) routes the internal linear solves through a LinearSolve.LinearCache. This enables symbolic-factorization reuse for sparse direct solvers (UMFPACK / KLU) and buffer reuse for dense (LU / GenericLU). The adjoint solve inside hessian reuses the same factors via cacheval' \ B (Group A algorithms only; Pardiso / MUMPS / CUDSS / STRUMPACK / cusolverRF would need per-library shims and are explicitly out of scope, tracked at SciML/LinearSolve.jl#92).

CI exercises four algorithms: LUFactorization, GenericLUFactorization, UMFPACKFactorization, KLUFactorization. Other LU-family algorithms (MKLLUFactorization, AppleAccelerateLUFactorization, RFLUFactorization, SparspakFactorization, ParUFactorization) should work via the same cacheval path, but the point of this PR isn't to test exhaustively — they can be added in a subsequent PR if requested.

Implementation notes

Two non-obvious wrinkles that surfaced during testing:

  1. LinearSolve.init(LinearProblem(J, B)) allocates LinearCache.u as a Vector{Float64} even when b is a Matrix{Float64}, so the subsequent solve! errors on the shape mismatch. Fixed by passing u0 = similar(B) to LinearProblem.
  2. GenericLUFactorization.cacheval is a Tuple{LU, ipiv} rather than a bare Factorization, so cacheval' \ B throws MethodError: adjoint(::Tuple{...}). Added a 2-method _factor helper that unwraps the tuple; LUFactorization/UMFPACKFactorization/KLUFactorization all hit the identity branch.

Test plan

  • Pkg.test() passes locally on Julia 1.12.6: dense (18 tests) + Optimization.jl extension (2) + sparse (31) = 51 pass, 0 fail.
  • linsolve = nothing row of dense_rosenbrock.jl is bit-identical to the deleted rosenbrock.jl tests (same model, same x0/p0, same oracles).
  • linsolve = nothing row of sparse_aibecs.jl is byte-identical to the prior AIBECS_test.jl assertions.
  • LUFactorization / GenericLUFactorization match the closed-form Rosenbrock derivatives.
  • UMFPACKFactorization / KLUFactorization match the ForwardDiff ground truth on the AIBECS P-model at both lambda and 2*lambda.
  • CI green (Julia 1 + Julia lts × macOS/ubuntu/windows = 6/6 passing).
  • Manual smoke test of the deprecation wrapper: with julia --depwarn=yes, initialize_mem(F, ∇ₓf, x₀, p₀, MyAlg()) emits Warning: initialize_mem is deprecated; use F1Cache(...) instead on stderr and returns an F1Method.F1Cache (not covered by Pkg.test() — initialize_mem is unreferenced by the test suite).

Generated with Claude Code

Adds an opt-in LinearSolve.jl-backed linear-solve path via a package
extension so LinearSolve remains a weak dependency. Default behaviour
(linsolve = nothing) is unchanged from 0.6.x.

Breaking renames (deprecation wrappers retained for 0.7.x; remove in 0.8):
  - Mem -> F1Cache
  - initialize_mem(...) -> F1Cache(...) constructor
  - update_mem! -> update_cache! (internal)
  - Mem.A field -> F1Cache.linear_cache

New:
  - Optional `linsolve` kwarg on the F1Cache constructor. With it,
    F1Cache.linear_cache holds a LinearSolve.LinearCache, enabling
    symbolic factorization reuse for sparse direct solvers and buffer
    reuse for dense.
  - Tested with LUFactorization, GenericLUFactorization,
    UMFPACKFactorization, KLUFactorization.

Tests:
  - Refactored test/sparse_aibecs.jl (renamed from AIBECS_test.jl) and
    test/dense_rosenbrock.jl (new) into a symmetric pattern: each loops
    over (nothing, ...) linsolve variants and compares against
    pre-computed ground-truth oracles (closed-form / ForwardDiff /
    FiniteDiff).
  - Dropped redundant test/rosenbrock.jl whose 6 assertions are now
    exercised by dense_rosenbrock.jl's linsolve = nothing arm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briochemc
briochemc merged commit 46aef76 into master May 13, 2026
6 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