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//! | [`Ilu0`] | CSC sparsity of `A` | `O(nnz(A))` | Zero-fill incomplete LU. |
15//! | [`Ic0`] | CSC lower triangle of `A` | `O(nnz_L)` | Zero-fill incomplete Cholesky for HPD `A`. |
16//! | [`Ilutp`] | CSC of `A` + threshold/fill params | `O(nnz_LU)` | Threshold ILU with partial pivoting; general nonsymmetric workhorse. |
17//! | [`SolvePrecond`] | any faer factorisation (`Llt`, `Lu`, `Qr`, ...) | factorisation-dependent | Adapter, not a factorisation. |
18//!
19//! # Choosing a preconditioner
20//!
21//! There is no single best preconditioner; the right one depends on the
22//! structure of `A` and how much work you can afford per iteration.
23//!
24//! - **Start with [`JacobiPrecond`].** It is almost free to build and apply,
25//! and it helps whenever `A`'s rows differ in scale — diagonally dominant
26//! systems, variable-coefficient PDEs, badly-scaled unknowns. If `A` has a
27//! constant diagonal it does nothing, so move on.
28//! - **[`Ic0`] for symmetric positive-definite `A`.** The standard choice for
29//! SPD problems from PDE discretisations (Laplacians, diffusion, elasticity)
30//! solved with conjugate gradient. It cuts iteration counts sharply; each
31//! apply costs two sparse triangular solves, so it always wins on iteration
32//! count and wins on wall-clock time once the problem is ill-conditioned
33//! enough to need many iterations.
34//! - **[`Ilu0`] for general (nonsymmetric) sparse `A`.** The nonsymmetric
35//! counterpart to IC(0), paired with GMRES or BiCGSTAB. Same zero-fill idea:
36//! cheap to build and stores nothing beyond `A`'s sparsity pattern.
37//! - **[`Ilutp`] when [`Ilu0`] is too weak.** Threshold ILU with partial
38//! pivoting: it adds fill where the factor needs it (tuned by a drop tolerance
39//! and a fill budget) and pivots for stability — the robust choice for hard
40//! nonsymmetric problems, badly-scaled operators, or matrices with small/zero
41//! diagonal entries. Costs more to build and apply than [`Ilu0`], and its
42//! pattern is value-dependent (no zero-allocation refactorisation).
43//! - **[`BlockJacobiPrecond`] when unknowns cluster into small dense groups.**
44//! Several fields per mesh node, coupled species, or tightly-coupled
45//! sub-systems. Inverting those blocks exactly captures the strong local
46//! coupling that point-Jacobi misses.
47//! - **[`SolvePrecond`] to reuse an exact factorisation.** Factorise a cheaper
48//! approximation of `A` once and let the Krylov method correct the rest.
49//!
50//! When `A`'s values change between iterations but its sparsity pattern does
51//! not — the inner loop of a nonlinear solver — build the symbolic factor once
52//! and call `refactorize` (see [`Ilu0`] and [`Ic0`]) to avoid reallocating.
53//!
54//! # Design contract
55//!
56//! - **No heap allocation during `apply`.** Every preconditioner returns
57//! either [`StackReq::EMPTY`](dyn_stack::StackReq::EMPTY) or a precise
58//! pre-computed scratch size from `apply_scratch` / `apply_in_place_scratch`.
59//! All temporary memory flows through the [`MemStack`](dyn_stack::MemStack)
60//! faer's trait interface provides.
61//! - **Refactorisation reuses storage.** [`Ilu0`] and [`Ic0`] expose a
62//! `refactorize(&mut self, a)` method for the steady-state case (nonlinear
63//! Krylov drivers where the sparsity pattern is fixed but the values
64//! change). It allocates nothing.
65//! - **In-place semantics match faer.** `apply` performs `out = M^{-1} rhs`;
66//! `apply_in_place` overwrites `rhs` with `M^{-1} rhs`. Transpose and
67//! adjoint variants follow the same convention against `M^{-T}` and
68//! `M^{-H}`.
69//!
70//! # Example: point-Jacobi
71//!
72//! ```
73//! use dyn_stack::MemStack;
74//! use faer::{mat, Par};
75//! use faer::matrix_free::Precond;
76//! use faer_precond::JacobiPrecond;
77//!
78//! let pc = JacobiPrecond::try_from_diagonal(&[4.0_f64, 2.0, 8.0]).unwrap();
79//!
80//! let mut x = mat![[8.0_f64], [6.0], [16.0]];
81//! pc.apply_in_place(x.as_mut(), Par::Seq, MemStack::new(&mut []));
82//!
83//! assert!((*x.as_ref().get(0, 0) - 2.0).abs() < 1e-12);
84//! assert!((*x.as_ref().get(1, 0) - 3.0).abs() < 1e-12);
85//! assert!((*x.as_ref().get(2, 0) - 2.0).abs() < 1e-12);
86//! ```
87//!
88//! # Example: incomplete LU on a sparse matrix
89//!
90//! ```
91//! use dyn_stack::MemStack;
92//! use faer::sparse::{SparseColMat, Triplet};
93//! use faer::{mat, Par};
94//! use faer::matrix_free::Precond;
95//! use faer_precond::Ilu0;
96//!
97//! // Build a 5x5 SPD tridiagonal: diag = 4, off = -1.
98//! let mut triplets = Vec::new();
99//! for i in 0..5 {
100//! triplets.push(Triplet::new(i, i, 4.0_f64));
101//! if i > 0 {
102//! triplets.push(Triplet::new(i, i - 1, -1.0));
103//! triplets.push(Triplet::new(i - 1, i, -1.0));
104//! }
105//! }
106//! let a = SparseColMat::<usize, f64>::try_new_from_triplets(5, 5, &triplets).unwrap();
107//!
108//! let pc = Ilu0::try_new(a.as_ref()).expect("non-singular pattern");
109//!
110//! // For a tridiagonal there is no fill, so ILU(0) is the exact LU.
111//! let mut b = mat![[1.0_f64], [0.0], [0.0], [0.0], [0.0]];
112//! pc.apply_in_place(b.as_mut(), Par::Seq, MemStack::new(&mut []));
113//! ```
114//!
115//! # Cargo features
116//!
117//! There are no opt-in feature flags in `0.1`. The crate inherits faer's
118//! default feature set, which includes sparse linear algebra (`sparse-linalg`)
119//! required by [`Ilu0`] and [`Ic0`].
120//!
121//! # License
122//!
123//! Dual-licensed under `MIT OR Apache-2.0`.
124
125#![doc(html_root_url = "https://docs.rs/faer-precond/0.1.0")]
126#![cfg_attr(docsrs, feature(doc_cfg))]
127
128pub mod adapters;
129pub mod block_jacobi;
130pub mod ic0;
131pub mod ilu0;
132pub mod ilutp;
133pub mod jacobi;
134
135pub use adapters::SolvePrecond;
136pub use block_jacobi::{BlockJacobiError, BlockJacobiPrecond};
137pub use ic0::{Ic0, Ic0Error, SymbolicIc0};
138pub use ilu0::{Ilu0, Ilu0Error, SymbolicIlu0};
139pub use ilutp::{FillControl, Ilutp, IlutpError, IlutpParams, RowNorm};
140pub use jacobi::{JacobiError, JacobiPrecond};