Solver backends

The built-in solver (CHOLMOD Cholesky with LDLᵀ fallback) needs nothing and is the default — most users never think about this. Loading LinearSolve.jl unlocks its entire algorithm collection through one keyword:

using LinearSolve

solve!(model; solver = KLUFactorization())      # alternative direct solver
solve!(model)                                   # choice is remembered on the model
model.cache.factorization.solver
KLUFactorization(true, true)

Iterative solvers suit large models:

p1 = Node([0.0, 0.0, 0.0], :fixed)
p2 = Node([0.0, 0.0, 3.0], :free)
pm = Model([p1, p2], [FrameElement(p1, p2, wshape)],
    [NodeForce(p2, [10.0, 0.0, 0.0])])
solve!(pm; solver = KrylovJL_CG())              # iterative — large models
displacement(pm.results, p2)
6-element StaticArraysCore.SVector{6, Float64} with indices SOneTo(6):
 0.005624999999999998
 7.175664838754022e-20
 0.0
 1.0763497258131032e-19
 0.0028124999999999995
 0.0

Repeated solves reuse the factorization's symbolic analysis (numeric-only refactorization on the frozen sparsity pattern) on every backend, including the default. Two notes: unpreconditioned iterative solvers want reasonably conditioned systems — supply a preconditioner for large/stiff models; and on the differentiable path, iterative solvers make gradients inexact adjoints (accuracy follows the solve tolerance) while direct factorizations stay exact.

(solve!/solve extend the CommonSolve verbs, so loading LinearSolve or other SciML packages never shadows Asap's API.)