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