Skip to main content

pounce_linsol/
sparse_sym_iface.rs

1//! Low-level sparse-symmetric backend interface — port of
2//! `IpSparseSymLinearSolverInterface.hpp`.
3//!
4//! Concrete implementors:
5//! * `pounce_hsl::Ma57SolverInterface` (v1.0).
6//! * Future: MUMPS, FERAL.
7
8use crate::status::ESymSolverStatus;
9use pounce_common::types::{Index, Number};
10
11/// Sparse matrix format that a backend wants its triplet/CSR data in.
12/// Mirrors `SparseSymLinearSolverInterface::EMatrixFormat`.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum EMatrixFormat {
15    /// Triplet (COO) of the lower triangle, 1-based indices
16    /// (MA27 / MA57 / MUMPS convention).
17    TripletFormat,
18    /// CSR of the upper triangle, 0-based indices.
19    CsrFormat0Offset,
20    /// CSR of the upper triangle, 1-based indices.
21    CsrFormat1Offset,
22    /// Full CSR (lower + upper), 0-based indices.
23    CsrFullFormat0Offset,
24    /// Full CSR (lower + upper), 1-based indices.
25    CsrFullFormat1Offset,
26}
27
28/// Backend-side trait. The lifecycle mirrors upstream's narrative
29/// comment in `IpSparseSymLinearSolverInterface.hpp`:
30///
31/// 1. caller asks [`Self::matrix_format`].
32/// 2. caller calls [`Self::initialize_structure`] once with `(ia, ja)`.
33/// 3. caller takes the values pointer from
34///    [`Self::values_array_mut`], fills it.
35/// 4. caller calls [`Self::multi_solve`] with `new_matrix=true` for
36///    each new value pattern.
37/// 5. caller may query [`Self::number_of_neg_evals`] /
38///    [`Self::increase_quality`] between solves.
39///
40/// `new_matrix=false` requests a back-substitution against the
41/// existing factorization.
42pub trait SparseSymLinearSolverInterface {
43    /// Initialize backend internal structures for a matrix of given
44    /// dimension and pattern.
45    fn initialize_structure(
46        &mut self,
47        dim: Index,
48        nonzeros: Index,
49        ia: &[Index],
50        ja: &[Index],
51    ) -> ESymSolverStatus;
52
53    /// Slice into which the caller writes the matrix nonzeros (in the
54    /// same order as `ja` from [`Self::initialize_structure`]).
55    fn values_array_mut(&mut self) -> &mut [Number];
56
57    /// Factor (if `new_matrix`) and back-substitute against `nrhs`
58    /// right-hand sides packed in `rhs_vals` (length `nrhs * dim`).
59    /// Solutions overwrite `rhs_vals`.
60    #[allow(clippy::too_many_arguments)]
61    fn multi_solve(
62        &mut self,
63        new_matrix: bool,
64        ia: &[Index],
65        ja: &[Index],
66        nrhs: Index,
67        rhs_vals: &mut [Number],
68        check_neg_evals: bool,
69        number_of_neg_evals: Index,
70    ) -> ESymSolverStatus;
71
72    /// Number of negative eigenvalues found in the most recent
73    /// factorization. Caller must check [`Self::provides_inertia`]
74    /// first.
75    fn number_of_neg_evals(&self) -> Index;
76
77    /// Ask the backend to use a more accurate (but slower) pivot
78    /// strategy on the next solve. Returns `false` if the maximum
79    /// quality is already reached.
80    fn increase_quality(&mut self) -> bool;
81
82    /// Whether this backend reports the number of negative
83    /// eigenvalues post-factor.
84    fn provides_inertia(&self) -> bool;
85
86    /// Required matrix layout. Caller marshals data into this format.
87    fn matrix_format(&self) -> EMatrixFormat;
88
89    /// Whether [`Self::determine_dependent_rows`] is supported.
90    fn provides_degeneracy_detection(&self) -> bool {
91        false
92    }
93
94    /// Find linearly dependent rows — used by Ipopt's degeneracy
95    /// probe. Default is `FatalError` matching upstream's default
96    /// implementation.
97    fn determine_dependent_rows(
98        &mut self,
99        _ia: &[Index],
100        _ja: &[Index],
101        _c_deps: &mut Vec<Index>,
102    ) -> ESymSolverStatus {
103        ESymSolverStatus::FatalError
104    }
105}