faer_precond/lib.rs
1//! Numerical preconditioners for iterative linear solvers, built on
2//! [faer](https://crates.io/crates/faer).
3//!
4//! Every preconditioner in this crate implements faer's
5//! `matrix_free::{LinOp, Precond, BiLinOp, BiPrecond}` traits, so they plug
6//! directly into faer's Krylov solvers (CG, GMRES, BiCGSTAB, LSMR, ...).
7//!
8//! # Available preconditioners
9//!
10//! | Type | Source | Apply cost | Notes |
11//! |---|---|---|---|
12//! | [`JacobiPrecond`] | diagonal of `A` | `O(n)` | Diagonally-dominant problems. |
13//! | [`BlockJacobiPrecond`] | dense diagonal blocks of `A` | `O(sum b_k²)` | Arbitrary block partition; LU per block. |
14//! | [`Ssor`] | `A`'s `D`/`L`/`U` split + relaxation | `O(nnz(A))` | Stationary; two triangular solves, no fill. |
15//! | [`Ilu0`] | CSC sparsity of `A` | `O(nnz(A))` | Zero-fill incomplete LU. |
16//! | [`Iluk`] | CSC of `A` + fill level `k` | `O(nnz_LU)` | Level-of-fill incomplete LU; between ILU(0) and ILUTP. |
17//! | [`Ic0`] | CSC lower triangle of `A` | `O(nnz_L)` | Zero-fill incomplete Cholesky for HPD `A`. |
18//! | [`Ict`] | CSC of `A` + threshold/fill params | `O(nnz_L)` | Threshold incomplete Cholesky; SPD analogue of ILUTP. |
19//! | [`Ilutp`] | CSC of `A` + threshold/fill params | `O(nnz_LU)` | Threshold ILU with partial pivoting; general nonsymmetric workhorse. |
20//! | [`Poly`] | `A` + polynomial degree/bounds | `O(degree · nnz(A))` | Matvec-only (Neumann/Chebyshev); no triangular solves. |
21//! | [`Fsai`] | CSC of `A` + pattern | `O(nnz_G)` | Factorised approximate inverse for HPD `A`; matvec apply. |
22//! | [`Spai`] | CSC of `A` + pattern | `O(nnz_M)` | Sparse approximate inverse (nonsymmetric); matvec apply. |
23//! | [`SolvePrecond`] | any faer factorisation (`Llt`, `Lu`, `Qr`, ...) | factorisation-dependent | Adapter, not a factorisation. |
24//!
25//! # Choosing a preconditioner
26//!
27//! There is no single best preconditioner; the right one depends on the
28//! structure of `A` and how much work you can afford per iteration.
29//!
30//! - **Start with [`JacobiPrecond`].** It is almost free to build and apply,
31//! and it helps whenever `A`'s rows differ in scale — diagonally dominant
32//! systems, variable-coefficient PDEs, badly-scaled unknowns. If `A` has a
33//! constant diagonal it does nothing, so move on.
34//! - **[`Ic0`] for symmetric positive-definite `A`.** The standard choice for
35//! SPD problems from PDE discretisations (Laplacians, diffusion, elasticity)
36//! solved with conjugate gradient. It cuts iteration counts sharply; each
37//! apply costs two sparse triangular solves, so it always wins on iteration
38//! count and wins on wall-clock time once the problem is ill-conditioned
39//! enough to need many iterations.
40//! - **[`Ilu0`] for general (nonsymmetric) sparse `A`.** The nonsymmetric
41//! counterpart to IC(0), paired with GMRES or BiCGSTAB. Same zero-fill idea:
42//! cheap to build and stores nothing beyond `A`'s sparsity pattern.
43//! - **[`Ilutp`] when [`Ilu0`] is too weak.** Threshold ILU with partial
44//! pivoting: it adds fill where the factor needs it (tuned by a drop tolerance
45//! and a fill budget) and pivots for stability — the robust choice for hard
46//! nonsymmetric problems, badly-scaled operators, or matrices with small/zero
47//! diagonal entries. Costs more to build and apply than [`Ilu0`], and its
48//! pattern is value-dependent (no zero-allocation refactorisation).
49//! - **[`BlockJacobiPrecond`] when unknowns cluster into small dense groups.**
50//! Several fields per mesh node, coupled species, or tightly-coupled
51//! sub-systems. Inverting those blocks exactly captures the strong local
52//! coupling that point-Jacobi misses.
53//! - **[`Ssor`] for a cheap, fill-free stationary preconditioner.** Built
54//! straight from `A`'s own triangles with one relaxation knob; a step up from
55//! point-Jacobi when you do not want to store a factorisation. Symmetric for
56//! SPD `A`, so it pairs with CG.
57//! - **[`Iluk`] when [`Ilu0`] is too weak but you want a fixed pattern.**
58//! Level-of-fill ILU: more accurate than ILU(0), but with a value-independent
59//! pattern (allocation-free refactorisation), unlike [`Ilutp`].
60//! - **[`Ict`] for hard SPD problems.** The threshold incomplete Cholesky:
61//! IC(0)'s adaptive cousin, for ill-conditioned SPD systems where zero-fill
62//! stalls. The SPD counterpart to [`Ilutp`].
63//! - **[`Poly`], [`Fsai`] or [`Spai`] when triangular solves are the
64//! bottleneck.** These apply through matrix-vector products only — no
65//! sequential forward/back substitution — so they parallelise far better on
66//! many cores or accelerators. [`Poly`] (Neumann/Chebyshev) and [`Fsai`] are
67//! for SPD `A`; [`Spai`] is the nonsymmetric approximate inverse. The trade-off
68//! is a weaker approximation per unit of single-core work; [`Poly`]'s
69//! Chebyshev form also needs a spectral-interval estimate.
70//! - **[`SolvePrecond`] to reuse an exact factorisation.** Factorise a cheaper
71//! approximation of `A` once and let the Krylov method correct the rest.
72//!
73//! When `A`'s values change between iterations but its sparsity pattern does
74//! not — the inner loop of a nonlinear solver — build the symbolic factor once
75//! and call `refactorize` (see [`Ilu0`] and [`Ic0`]) to avoid reallocating.
76//!
77//! # Design contract
78//!
79//! - **No heap allocation during `apply`.** Every preconditioner returns
80//! either [`StackReq::EMPTY`](dyn_stack::StackReq::EMPTY) or a precise
81//! pre-computed scratch size from `apply_scratch` / `apply_in_place_scratch`.
82//! All temporary memory flows through the [`MemStack`](dyn_stack::MemStack)
83//! faer's trait interface provides.
84//! - **Refactorisation reuses storage.** [`Ilu0`] and [`Ic0`] expose a
85//! `refactorize(&mut self, a)` method for the steady-state case (nonlinear
86//! Krylov drivers where the sparsity pattern is fixed but the values
87//! change). It allocates nothing.
88//! - **In-place semantics match faer.** `apply` performs `out = M^{-1} rhs`;
89//! `apply_in_place` overwrites `rhs` with `M^{-1} rhs`. Transpose and
90//! adjoint variants follow the same convention against `M^{-T}` and
91//! `M^{-H}`.
92//!
93//! # Example: point-Jacobi
94//!
95//! ```
96//! use dyn_stack::MemStack;
97//! use faer::{mat, Par};
98//! use faer::matrix_free::Precond;
99//! use faer_precond::JacobiPrecond;
100//!
101//! let pc = JacobiPrecond::try_from_diagonal(&[4.0_f64, 2.0, 8.0]).unwrap();
102//!
103//! let mut x = mat![[8.0_f64], [6.0], [16.0]];
104//! pc.apply_in_place(x.as_mut(), Par::Seq, MemStack::new(&mut []));
105//!
106//! assert!((*x.as_ref().get(0, 0) - 2.0).abs() < 1e-12);
107//! assert!((*x.as_ref().get(1, 0) - 3.0).abs() < 1e-12);
108//! assert!((*x.as_ref().get(2, 0) - 2.0).abs() < 1e-12);
109//! ```
110//!
111//! # Example: incomplete LU on a sparse matrix
112//!
113//! ```
114//! use dyn_stack::MemStack;
115//! use faer::sparse::{SparseColMat, Triplet};
116//! use faer::{mat, Par};
117//! use faer::matrix_free::Precond;
118//! use faer_precond::Ilu0;
119//!
120//! // Build a 5x5 SPD tridiagonal: diag = 4, off = -1.
121//! let mut triplets = Vec::new();
122//! for i in 0..5 {
123//! triplets.push(Triplet::new(i, i, 4.0_f64));
124//! if i > 0 {
125//! triplets.push(Triplet::new(i, i - 1, -1.0));
126//! triplets.push(Triplet::new(i - 1, i, -1.0));
127//! }
128//! }
129//! let a = SparseColMat::<usize, f64>::try_new_from_triplets(5, 5, &triplets).unwrap();
130//!
131//! let pc = Ilu0::try_new(a.as_ref()).expect("non-singular pattern");
132//!
133//! // For a tridiagonal there is no fill, so ILU(0) is the exact LU.
134//! let mut b = mat![[1.0_f64], [0.0], [0.0], [0.0], [0.0]];
135//! pc.apply_in_place(b.as_mut(), Par::Seq, MemStack::new(&mut []));
136//! ```
137//!
138//! # Cargo features
139//!
140//! There are no opt-in feature flags in `0.1`. The crate inherits faer's
141//! default feature set, which includes sparse linear algebra (`sparse-linalg`)
142//! required by [`Ilu0`] and [`Ic0`].
143//!
144//! # License
145//!
146//! Dual-licensed under `MIT OR Apache-2.0`.
147
148#![doc(html_root_url = "https://docs.rs/faer-precond/0.2.0")]
149#![cfg_attr(docsrs, feature(doc_cfg))]
150
151pub mod adapters;
152pub mod block_jacobi;
153pub mod fsai;
154pub mod ic0;
155pub mod ict;
156pub mod ilu0;
157pub mod iluk;
158pub mod ilutp;
159pub mod jacobi;
160pub mod poly;
161pub mod spai;
162pub mod ssor;
163mod util;
164
165pub use adapters::SolvePrecond;
166pub use block_jacobi::{BlockJacobiError, BlockJacobiPrecond};
167pub use fsai::{Fsai, FsaiError, FsaiPattern};
168pub use ic0::{Ic0, Ic0Error, SymbolicIc0};
169pub use ict::{Ict, IctError, IctParams};
170pub use ilu0::{Ilu0, Ilu0Error, SymbolicIlu0};
171pub use iluk::{Iluk, IlukError, IlukParams, SymbolicIluk};
172pub use ilutp::{FillControl, Ilutp, IlutpError, IlutpParams, RowNorm};
173pub use jacobi::{JacobiError, JacobiPrecond};
174pub use poly::{BoundEstimate, Poly, PolyError, PolyKind, PolyParams};
175pub use spai::{Spai, SpaiError, SpaiPattern};
176pub use ssor::{Ssor, SsorError, SsorParams};