Internal API Documentation
This page documents LinearSolve.jl's internal API, which is useful for developers who want to understand the package's architecture, contribute to the codebase, or develop custom linear solver algorithms.
Abstract Type Hierarchy
LinearSolve.jl uses a well-structured type hierarchy to organize different classes of linear solver algorithms:
LinearSolve.SciMLLinearSolveAlgorithm
— TypeSciMLLinearSolveAlgorithm <: SciMLBase.AbstractLinearAlgorithm
The root abstract type for all linear solver algorithms in LinearSolve.jl. All concrete linear solver implementations should inherit from one of the specialized subtypes rather than directly from this type.
This type integrates with the SciMLBase ecosystem, providing a consistent interface for linear algebra operations across the Julia scientific computing ecosystem.
LinearSolve.AbstractFactorization
— TypeAbstractFactorization <: SciMLLinearSolveAlgorithm
Abstract type for linear solvers that work by computing a matrix factorization. These algorithms typically decompose the matrix A
into a product of simpler matrices (e.g., A = LU
, A = QR
, A = LDL'
) and then solve the system using forward/backward substitution.
Characteristics
- Requires concrete matrix representation (
needs_concrete_A() = true
) - Typically efficient for multiple solves with the same matrix
- Generally provides high accuracy for well-conditioned problems
- Memory requirements depend on the specific factorization type
Subtypes
AbstractDenseFactorization
: For dense matrix factorizationsAbstractSparseFactorization
: For sparse matrix factorizations
Examples of concrete subtypes
LUFactorization
,QRFactorization
,CholeskyFactorization
UMFPACKFactorization
,KLUFactorization
LinearSolve.AbstractDenseFactorization
— TypeAbstractDenseFactorization <: AbstractFactorization
Abstract type for factorization-based linear solvers optimized for dense matrices. These algorithms assume the matrix has no particular sparsity structure and use dense linear algebra routines (typically from BLAS/LAPACK) for optimal performance.
Characteristics
- Optimized for matrices with few zeros or no sparsity structure
- Leverage highly optimized BLAS/LAPACK routines when available
- Generally provide excellent performance for moderately-sized dense problems
- Memory usage scales as O(n²) with matrix size
Examples of concrete subtypes
LUFactorization
: Dense LU with partial pivoting (via LAPACK)QRFactorization
: Dense QR factorization for overdetermined systemsCholeskyFactorization
: Dense Cholesky for symmetric positive definite matricesBunchKaufmanFactorization
: For symmetric indefinite matrices
LinearSolve.AbstractSparseFactorization
— TypeAbstractSparseFactorization <: AbstractFactorization
Abstract type for factorization-based linear solvers optimized for sparse matrices. These algorithms take advantage of sparsity patterns to reduce memory usage and computational cost compared to dense factorizations.
Characteristics
- Optimized for matrices with many zero entries
- Often use specialized pivoting strategies to preserve sparsity
- May reorder rows/columns to minimize fill-in during factorization
- Typically more memory-efficient than dense methods for sparse problems
Examples of concrete subtypes
UMFPACKFactorization
: General sparse LU with partial pivotingKLUFactorization
: Sparse LU optimized for circuit simulationCHOLMODFactorization
: Sparse Cholesky for positive definite systemsSparspakFactorization
: Envelope/profile method for sparse systems
LinearSolve.AbstractKrylovSubspaceMethod
— TypeAbstractKrylovSubspaceMethod <: SciMLLinearSolveAlgorithm
Abstract type for iterative linear solvers based on Krylov subspace methods. These algorithms solve linear systems by iteratively building an approximation from a sequence of Krylov subspaces, without requiring explicit matrix factorization.
Characteristics
- Does not require concrete matrix representation (
needs_concrete_A() = false
) - Only needs matrix-vector products
A*v
(can work with operators/functions) - Memory usage typically O(n) or O(kn) where k << n
- Convergence depends on matrix properties (condition number, eigenvalue distribution)
- Often benefits significantly from preconditioning
Advantages
- Low memory requirements for large sparse systems
- Can handle matrix-free operators (functions that compute
A*v
) - Often the only feasible approach for very large systems
- Can exploit matrix structure through specialized operators
Examples of concrete subtypes
GMRESIteration
: Generalized Minimal Residual methodCGIteration
: Conjugate Gradient (for symmetric positive definite systems)BiCGStabLIteration
: Bi-Conjugate Gradient Stabilized- Wrapped external iterative solvers (KrylovKit.jl, IterativeSolvers.jl)
LinearSolve.AbstractSolveFunction
— TypeAbstractSolveFunction <: SciMLLinearSolveAlgorithm
Abstract type for linear solvers that wrap custom solving functions or provide direct interfaces to specific solve methods. These provide flexibility for integrating custom algorithms or simple solve strategies.
Characteristics
- Does not require concrete matrix representation (
needs_concrete_A() = false
) - Provides maximum flexibility for custom solving strategies
- Can wrap external solver libraries or implement specialized algorithms
- Performance and stability depend entirely on the wrapped implementation
Examples of concrete subtypes
LinearSolveFunction
: Wraps arbitrary user-defined solve functionsDirectLdiv!
: Direct application of the\
operator
Core Cache System
The caching system is central to LinearSolve.jl's performance and functionality:
LinearSolve.LinearCache
— TypeLinearCache{TA, Tb, Tu, Tp, Talg, Tc, Tl, Tr, Ttol, issq, S}
The core cache structure used by LinearSolve for storing and managing the state of linear solver computations. This mutable struct acts as the primary interface for iterative solving and caching of factorizations and intermediate results.
Fields
A::TA
: The matrix operator of the linear system.b::Tb
: The right-hand side vector of the linear system.u::Tu
: The solution vector (preallocated storage for the result).p::Tp
: Parameters passed to the linear solver algorithm.alg::Talg
: The linear solver algorithm instance.cacheval::Tc
: Algorithm-specific cache storage for factorizations and intermediate computations.isfresh::Bool
: Cache validity flag for the matrixA
.false
meanscacheval
is up-to-date with respect toA
,true
meanscacheval
needs to be updated.precsisfresh::Bool
: Cache validity flag for preconditioners.false
meansPl
andPr
are up-to-date with respect toA
,true
means they need to be updated.Pl::Tl
: Left preconditioner operator.Pr::Tr
: Right preconditioner operator.abstol::Ttol
: Absolute tolerance for iterative solvers.reltol::Ttol
: Relative tolerance for iterative solvers.maxiters::Int
: Maximum number of iterations for iterative solvers.verbose::LinearVerbosity
: Whether to print verbose output during solving.assumptions::OperatorAssumptions{issq}
: Assumptions about the operator properties.sensealg::S
: Sensitivity analysis algorithm for automatic differentiation.
Usage
The LinearCache
is typically created via init(::LinearProblem, ::SciMLLinearSolveAlgorithm)
and then used with solve!(cache)
for efficient repeated solves with the same matrix structure but potentially different right-hand sides or parameter values.
Cache Management
The cache automatically tracks when matrix A
or parameters p
change by setting the appropriate freshness flags. When solve!
is called, stale cache entries are automatically recomputed as needed.
LinearSolve.init_cacheval
— Functioninit_cacheval(alg::SciMLLinearSolveAlgorithm, args...)
Initialize algorithm-specific cache values for the given linear solver algorithm. This function returns nothing
by default and is intended to be overloaded by specific algorithm implementations that need to store intermediate computations or factorizations.
Arguments
alg
: The linear solver algorithm instanceargs...
: Additional arguments passed to the cache initialization
Returns
Algorithm-specific cache value or nothing
for algorithms that don't require caching.
cache.cacheval = NamedTuple(LUFactorization = cache of LUFactorization, ...)
Algorithm Selection
The automatic algorithm selection is one of LinearSolve.jl's key features:
Missing docstring for LinearSolve.defaultalg
. Check Documenter's build log for details.
LinearSolve.get_tuned_algorithm
— Functionget_tuned_algorithm(::Type{eltype_A}, ::Type{eltype_b}, matrix_size) where {eltype_A, eltype_b}
Get the tuned algorithm preference for the given element type and matrix size. Returns nothing
if no preference exists. Uses preloaded constants for efficiency. Fast path when no preferences are set.
LinearSolve.is_algorithm_available
— Functionis_algorithm_available(alg::DefaultAlgorithmChoice.T)
Check if the given algorithm is currently available (extensions loaded, etc.).
LinearSolve.show_algorithm_choices
— Functionshow_algorithm_choices()
Display what algorithm choices are actually made by the default solver for representative matrix sizes. Shows current preferences and system information.
LinearSolve.make_preferences_dynamic!
— Functionmake_preferences_dynamic!()
Internal function for testing only. Makes preferences dynamic by redefining gettunedalgorithm to check preferences at runtime instead of using compile-time constants. This allows tests to verify that the preference system works correctly.
Preference System Architecture
The dual preference system provides intelligent algorithm selection with comprehensive fallbacks:
Core Functions
get_tuned_algorithm
: Retrieves tuned algorithm preferences based on matrix size and element typeis_algorithm_available
: Checks if a specific algorithm is currently available (extensions loaded)show_algorithm_choices
: Analysis function displaying algorithm choices for all element typesmake_preferences_dynamic!
: Testing function that enables runtime preference checking
Size Categorization
The system categorizes matrix sizes to match LinearSolveAutotune benchmarking:
- tiny: ≤20 elements (matrices ≤10 always override to GenericLU)
- small: 21-100 elements
- medium: 101-300 elements
- large: 301-1000 elements
- big: >1000 elements
Dual Preference Structure
For each category and element type (Float32, Float64, ComplexF32, ComplexF64):
best_algorithm_{type}_{size}
: Overall fastest algorithm from autotunebest_always_loaded_{type}_{size}
: Fastest always-available algorithm (fallback)
Preference File Organization
All preference-related functionality is consolidated in src/preferences.jl
:
Compile-Time Constants:
AUTOTUNE_PREFS
: Preference structure loaded at package importAUTOTUNE_PREFS_SET
: Fast path check for whether any preferences are set_string_to_algorithm_choice
: Mapping from preference strings to algorithm enums
Runtime Functions:
_get_tuned_algorithm_runtime
: Dynamic preference checking for testing_choose_available_algorithm
: Algorithm availability and fallback logicshow_algorithm_choices
: Comprehensive analysis and display function
Testing Infrastructure:
make_preferences_dynamic!
: Eval-based function redefinition for testing- Enables runtime preference verification without affecting production performance
Testing Mode Operation
The testing system uses an elegant eval-based approach:
# Production: Uses compile-time constants (maximum performance)
get_tuned_algorithm(Float64, Float64, 200) # → Uses AUTOTUNE_PREFS constants
# Testing: Redefines function to use runtime checking
make_preferences_dynamic!()
get_tuned_algorithm(Float64, Float64, 200) # → Uses runtime preference loading
This approach maintains type stability and inference while enabling comprehensive testing.
Algorithm Support Scope
The preference system focuses exclusively on LU algorithms for dense matrices:
Supported LU Algorithms:
LUFactorization
,GenericLUFactorization
,RFLUFactorization
MKLLUFactorization
,AppleAccelerateLUFactorization
SimpleLUFactorization
,FastLUFactorization
(both map to LU)- GPU LU variants (CUDA, Metal, AMDGPU - all map to LU)
Non-LU algorithms (QR, Cholesky, SVD, etc.) are not included in the preference system as they serve different use cases and are not typically the focus of dense matrix autotune optimization.
Trait Functions
These trait functions help determine algorithm capabilities and requirements:
LinearSolve.needs_concrete_A
— Functionneeds_concrete_A(alg) -> Bool
Trait function that determines whether a linear solver algorithm requires a concrete matrix representation or can work with abstract operators.
Arguments
alg
: A linear solver algorithm instance
Returns
true
: Algorithm requires a concrete matrix (e.g., for factorization)false
: Algorithm can work with abstract operators (e.g., matrix-free methods)
Usage
This trait is used internally by LinearSolve.jl to optimize algorithm dispatch and determine when matrix operators need to be converted to concrete arrays.
Algorithm-Specific Behavior
AbstractFactorization
:true
(needs explicit matrix entries for factorization)AbstractKrylovSubspaceMethod
:false
(only needs matrix-vector products)AbstractSolveFunction
:false
(depends on the wrapped function's requirements)
Example
needs_concrete_A(LUFactorization()) # true
needs_concrete_A(GMRESIteration()) # false
Utility Functions
Various utility functions support the core functionality:
LinearSolve.default_tol
— Functiondefault_tol(T)
Compute the default tolerance for iterative linear solvers based on the element type. The tolerance is typically set as the square root of the machine epsilon for the given floating point type, ensuring numerical accuracy appropriate for that precision.
Arguments
T
: The element type of the linear system
Returns
- For floating point types:
√(eps(T))
- For exact types (Rational, Integer):
0
(exact arithmetic) - For Any type:
0
(conservative default)
LinearSolve.default_alias_A
— Functiondefault_alias_A(alg, A, b) -> Bool
Determine the default aliasing behavior for the matrix A
given the algorithm type. Aliasing allows the algorithm to modify the original matrix in-place for efficiency, but this may not be desirable or safe for all algorithm types.
Arguments
alg
: The linear solver algorithmA
: The matrix operatorb
: The right-hand side vector
Returns
false
: Safe default, algorithm will not modify the original matrixA
true
: Algorithm may modifyA
in-place for efficiency
Algorithm-Specific Behavior
- Dense factorizations:
false
(destructive, need to preserve original) - Krylov methods:
true
(non-destructive, safe to alias) - Sparse factorizations:
true
(typically preserve sparsity structure)
LinearSolve.default_alias_b
— Functiondefault_alias_b(alg, A, b) -> Bool
Determine the default aliasing behavior for the right-hand side vector b
given the algorithm type. Similar to default_alias_A
but for the RHS vector.
Returns
false
: Safe default, algorithm will not modify the original vectorb
true
: Algorithm may modifyb
in-place for efficiency
LinearSolve.__init_u0_from_Ab
— Function__init_u0_from_Ab(A, b)
Initialize the solution vector u0
with appropriate size and type based on the matrix A
and right-hand side b
. The solution vector is allocated with the same element type as b
and sized to match the number of columns in A
.
Arguments
A
: The matrix operator (determines solution vector size)b
: The right-hand side vector (determines element type)
Returns
A zero-initialized vector of size (size(A, 2),)
with element type matching b
.
Specializations
- For static matrices (
SMatrix
): Returns a static vector (SVector
) - For regular matrices: Returns a similar vector to
b
with appropriate size
Solve Functions
For custom solving strategies:
LinearSolve.LinearSolveFunction
— TypeLinearSolveFunction{F} <: AbstractSolveFunction
A flexible wrapper that allows using custom functions as linear solver algorithms. This provides a way to integrate user-defined solving strategies into the LinearSolve.jl framework while maintaining compatibility with the caching and interface systems.
Fields
solve_func::F
: A callable that implements the custom linear solving logic
Function Signature
The wrapped function should have the signature:
solve_func(A, b, u, p, isfresh, Pl, Pr, cacheval; kwargs...)
Arguments to wrapped function
A
: The matrix operator of the linear systemb
: The right-hand side vectoru
: Pre-allocated solution vector (can be used as working space)p
: Parameters passed to the solverisfresh
: Boolean indicating if the matrixA
has changed since last solvePl
: Left preconditioner operatorPr
: Right preconditioner operatorcacheval
: Algorithm-specific cache storagekwargs...
: Additional keyword arguments
Returns
The wrapped function should return a solution vector.
Example
function my_custom_solver(A, b, u, p, isfresh, Pl, Pr, cacheval; kwargs...)
# Custom solving logic here
return A \ b # Simple example
end
alg = LinearSolveFunction(my_custom_solver)
sol = solve(prob, alg)
LinearSolve.DirectLdiv!
— TypeDirectLdiv! <: AbstractSolveFunction
A simple linear solver that directly applies the left-division operator (\
) to solve the linear system. This algorithm calls ldiv!(u, A, b)
which computes u = A \ b
in-place.
Usage
alg = DirectLdiv!()
sol = solve(prob, alg)
Notes
- This is essentially a direct wrapper around Julia's built-in
ldiv!
function - Suitable for cases where the matrix
A
has a natural inverse or factorization - Performance depends on the specific matrix type and its
ldiv!
implementation - No preconditioners or advanced numerical techniques are applied
- Best used for small to medium problems or when
A
has special structure
Preconditioner Infrastructure
The preconditioner system allows for flexible preconditioning strategies:
LinearSolve.ComposePreconditioner
— TypeComposePreconditioner{Ti, To}
A preconditioner that composes two preconditioners by applying them sequentially. The inner preconditioner is applied first, followed by the outer preconditioner. This allows for building complex preconditioning strategies by combining simpler ones.
Fields
inner::Ti
: The inner (first) preconditioner to applyouter::To
: The outer (second) preconditioner to apply
Usage
# Compose a diagonal preconditioner with an ILU preconditioner
inner_prec = DiagonalPreconditioner(diag(A))
outer_prec = ILUFactorization()
composed = ComposePreconditioner(inner_prec, outer_prec)
The composed preconditioner applies: outer(inner(x))
for any vector x
.
Mathematical Interpretation
For a linear system Ax = b
, if P₁
is the inner and P₂
is the outer preconditioner, then the composed preconditioner effectively applies P₂P₁
as the combined preconditioner.
LinearSolve.InvPreconditioner
— TypeInvPreconditioner{T}
A preconditioner wrapper that treats a matrix or operator as if it represents the inverse of the actual preconditioner. Instead of solving Px = y
, it computes P*y
where P
is stored as the "inverse" preconditioner matrix.
Fields
P::T
: The stored preconditioner matrix/operator (representingP⁻¹
)
Usage
This is useful when you have a matrix that approximates the inverse of your desired preconditioner. For example, if you have computed an approximate inverse matrix Ainv ≈ A⁻¹
, you can use:
prec = InvPreconditioner(Ainv)
Mathematical Interpretation
For a linear system Ax = b
with preconditioner M
, normally we solve M⁻¹Ax = M⁻¹b
. With InvPreconditioner
, the stored matrix P
represents M⁻¹
directly, so applying the preconditioner becomes a matrix-vector multiplication rather than a linear solve.
Methods
ldiv!(A::InvPreconditioner, x)
: Computesx ← P*x
(in-place)ldiv!(y, A::InvPreconditioner, x)
: Computesy ← P*x
mul!(y, A::InvPreconditioner, x)
: Computesy ← P⁻¹*x
(inverse operation)
Internal Algorithm Types
These are internal algorithm implementations:
LinearSolve.SimpleLUFactorization
— TypeSimpleLUFactorization(pivot::Bool = true)
A pure Julia LU factorization implementation without BLAS dependencies. This solver is optimized for small matrices and situations where BLAS is not available or desirable.
Constructor Arguments
pivot::Bool = true
: Whether to perform partial pivoting for numerical stability. Set tofalse
for slightly better performance at the cost of stability.
Features
- Pure Julia implementation (no BLAS dependencies)
- Partial pivoting support for numerical stability
- In-place matrix modification for memory efficiency
- Fast for small matrices (typically < 100×100)
- Educational value for understanding LU factorization
Performance Characteristics
- Optimal for small dense matrices
- No overhead from BLAS calls
- Linear scaling with problem size (O(n³) operations)
- Memory efficient due to in-place operations
Use Cases
- Small matrices where BLAS overhead is significant
- Systems without optimized BLAS libraries
- Educational and prototyping purposes
- Embedded systems with memory constraints
Example
# Stable version with pivoting (default)
alg1 = SimpleLUFactorization()
# Faster version without pivoting
alg2 = SimpleLUFactorization(false)
prob = LinearProblem(A, b)
sol = solve(prob, alg1)
Notes
For larger matrices (> 100×100), consider using BLAS-based factorizations like LUFactorization()
for better performance.
LinearSolve.LUSolver
— TypeLUSolver{T}
A mutable workspace for performing LU factorization and solving linear systems. This struct maintains all necessary arrays and state information for the factorization and solve phases, allowing for efficient reuse when solving multiple systems with the same matrix structure.
Fields
n::Int
: Dimension of the square matrixA::Matrix{T}
: Working copy of the matrix to be factorized (modified in-place)b::Vector{T}
: Right-hand side vector storagex::Vector{T}
: Solution vector storagepivots::Vector{Int}
: Pivot indices from the factorizationperms::Vector{Int}
: Permutation vector tracking row exchangesinfo::Int
: Status information (0 = success, >0 indicates singularity)
Constructor
LUSolver{T}(n) # Create solver for n×n matrix with element type T
Usage
The solver is typically created from a matrix using the convenience constructors:
solver = LUSolver(A) # From matrix A
solver = LUSolver(A, b) # From matrix A and RHS b
Then factorized and solved:
simplelu_factorize!(solver) # Perform LU factorization
simplelu_solve!(solver) # Solve for the stored RHS
Notes
This is a pure Julia implementation primarily for educational purposes and small matrices. For production use, prefer optimized LAPACK-based factorizations.
Developer Notes
Adding New Algorithms
When adding a new linear solver algorithm to LinearSolve.jl:
- Choose the appropriate abstract type: Inherit from the most specific abstract type that fits your algorithm
- Implement required methods: At minimum, implement
solve!
and possiblyinit_cacheval
- Consider trait functions: Override trait functions like
needs_concrete_A
if needed - Document thoroughly: Add comprehensive docstrings following the patterns shown here
Performance Considerations
- The
LinearCache
system is designed for efficient repeated solves - Use
cache.isfresh
to avoid redundant computations when the matrix hasn't changed - Consider implementing specialized
init_cacheval
for algorithms that need setup - Leverage trait functions to optimize dispatch and memory usage
Testing Guidelines
When adding new functionality:
- Test with various matrix types (dense, sparse, GPU arrays)
- Verify caching behavior works correctly
- Ensure trait functions return appropriate values
- Test integration with the automatic algorithm selection system