Skip to main content

dune_core/linalg/
mod.rs

1//! Linear solver abstraction (D-0005). Devices never see the backend;
2//! nothing outside `linalg::backend` may call `faer` or
3//! `xingo-sparse` directly.
4//!
5//! The split mirrors the 3f5's SMP layer: `analyze` fixes the
6//! sparsity pattern (`SMPpreOrder`), `factor` refactors with new
7//! values and the diagonal gmin of `CKTop`'s gmin stepping
8//! (`SMPluFac` + `LoadGmin`), `solve_in_place` back-substitutes
9//! (`SMPsolve`).
10
11pub mod backend;
12
13/// Matrix sparsity pattern: `n`×`n`, ground already removed
14/// (0-based). `entries` are unique positions; values passed to
15/// [`LinearSolver::factor`] are parallel to it.
16#[derive(Clone, Debug)]
17pub struct Pattern {
18    pub n: usize,
19    pub entries: Vec<(usize, usize)>,
20}
21
22#[derive(Debug, thiserror::Error)]
23pub enum LinalgError {
24    /// Structural or numerical singularity; `row` is the 0-based
25    /// matrix row (caller maps it back to a node name).
26    #[error("singular matrix at row {row}")]
27    Singular { row: usize },
28}
29
30pub trait LinearSolver {
31    /// Fix the pattern for subsequent factorizations.
32    fn analyze(&mut self, pattern: &Pattern);
33
34    /// Factor with `values` (parallel to the pattern's entries).
35    /// `diag_gmin` is added to every diagonal position present in
36    /// the pattern, exactly like the 3f5's `LoadGmin`.
37    fn factor(&mut self, values: &[f64], diag_gmin: f64) -> Result<(), LinalgError>;
38
39    /// Solve `A x = rhs`, overwriting `rhs` with `x`.
40    fn solve_in_place(&mut self, rhs: &mut [f64]) -> Result<(), LinalgError>;
41
42    /// Factor the complex matrix `G + jB` over the same pattern
43    /// (`SMPcLUfac`); `re`/`im` are parallel to the pattern entries.
44    fn factor_complex(&mut self, re: &[f64], im: &[f64]) -> Result<(), LinalgError>;
45
46    /// Solve the complex system in place (`SMPcSolve`).
47    fn solve_complex_in_place(
48        &mut self,
49        rhs_re: &mut [f64],
50        rhs_im: &mut [f64],
51    ) -> Result<(), LinalgError>;
52
53    /// Solve the **transposed** complex system in place
54    /// (`SMPcaSolve`, the noise adjoint). Requires a prior
55    /// [`LinearSolver::factor_complex`].
56    fn solve_complex_transposed_in_place(
57        &mut self,
58        rhs_re: &mut [f64],
59        rhs_im: &mut [f64],
60    ) -> Result<(), LinalgError>;
61
62    /// Determinant of the complex matrix `G + jB` as a base-2
63    /// mantissa/exponent pair (`SMPcLUfac` + `SMPcDProd`): the
64    /// mantissa is normalized so `max(|re|,|im|) ∈ (0.5, 1]` like
65    /// the 3f5's `C_NORM`. A singular matrix returns a zero
66    /// mantissa — the pole-zero search treats that as a root.
67    fn det_complex(&mut self, re: &[f64], im: &[f64]) -> (num_complex::Complex64, i32);
68}
69
70/// The backend for this build: the dense faer LU by default; the
71/// `solver-xingo` feature switches every engine to the xingo-sparse LU
72/// (D-0005; the choice of default is P-3, still open — enabling
73/// `solver-xingo` is an explicit opt-in, not a new default).
74pub fn default_solver() -> Box<dyn LinearSolver> {
75    #[cfg(feature = "solver-xingo")]
76    {
77        Box::new(backend::xingo_sparse::XingoSparse::default())
78    }
79    #[cfg(all(feature = "solver-faer", not(feature = "solver-xingo")))]
80    {
81        Box::new(backend::faer_dense::FaerDense::default())
82    }
83    #[cfg(not(any(feature = "solver-faer", feature = "solver-xingo")))]
84    {
85        compile_error!(
86            "dune-core needs a linear solver backend (enable the `solver-faer` or the `solver-xingo` feature)"
87        );
88    }
89}