1use std::{
3 error::Error, fmt
4};
5#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct Tolerance {
9 pub absolute: f64, pub relative: f64
10}
11impl Default for Tolerance {
12 fn default() -> Self {
13 Self {
14 absolute: 0.0, relative: 1e-12
15 }
16 }
17}
18impl Tolerance {
19 pub const EXACT: Self = Self {
20 absolute: 0.0, relative: 0.0
21 };
22 pub fn validate(self) -> Result<(), SolverError> {
23 if !self.absolute.is_finite() || !self.relative.is_finite()
24 || self.absolute < 0.0 || self.relative < 0.0 {
25 Err(SolverError::InvalidOption("tolerance must be finite and nonnegative"))
26 } else {
27 Ok(())
28 }
29 }
30 pub(crate) fn threshold(self, scale: f64) -> Result<f64, SolverError> {
31 self.validate()?;
32 let value = self.absolute.max(self.relative * scale);
33 if !scale.is_finite() || scale < 0.0 || !value.is_finite() {
34 Err(SolverError::Arithmetic("tolerance scale overflow"))
35 } else {
36 Ok(value)
37 }
38 }
39}
40#[derive(Clone, Debug, PartialEq)]
41pub enum SolverError {
42 Shape(&'static str),
43 WorkspaceLimit { required: usize, limit: usize },
44 Communication(String),
45 SizeOverflow,
46 Allocation,
47 NonFinite {
48 index: usize
49 },
50 InvalidOption(&'static str),
51 Singular {
52 index: usize, pivot: f64, threshold: f64
53 },
54 NotSymmetric {
55 row: usize, column: usize
56 },
57 NotPositiveDefinite {
58 index: usize, pivot: f64
59 },
60 RankDeficient {
61 rank: usize, columns: usize
62 },
63 Arithmetic(&'static str),
64 Breakdown(&'static str),
65 NonConvergence {
66 iterations: usize, residual: f64
67 },
68 Operator(&'static str),
69}
70impl fmt::Display for SolverError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Self::WorkspaceLimit { required, limit } => write!(f, "workspace fill {required} exceeds limit {limit}"),
74 Self::Communication(s) => write!(f, "solver communication error: {s}"),
75 Self::Shape(s) => write!(f, "invalid matrix/vector shape: {s}"),
76 Self::SizeOverflow => write!(f, "matrix size or address range overflows usize"),
77 Self::Allocation => write!(f, "numerical workspace allocation failed"),
78 Self::NonFinite {
79 index
80 } => write!(f, "nonfinite input at logical index {index}"),
81 Self::InvalidOption(s) => write!(f, "invalid solver option: {s}"),
82 Self::Singular {
83 index, pivot, threshold
84 } => write!(f, "singular/numerically singular pivot {index}: |{pivot}| <= {threshold}"),
85 Self::NotSymmetric {
86 row, column
87 } => write!(f, "matrix differs across ({row},{column}) and its transpose"),
88 Self::NotPositiveDefinite {
89 index, pivot
90 } => write!(f, "nonpositive Cholesky pivot {index}: {pivot}"),
91 Self::RankDeficient {
92 rank, columns
93 } => write!(f, "numerical column rank {rank} < {columns}; this QR path requires full column rank; use Svd for a minimum-norm solve"),
94 Self::Arithmetic(s) => write!(f, "nonfinite/unsupported numerical intermediate: {s}"),
95 Self::Breakdown(s) => write!(f, "iterative solver breakdown: {s}"),
96 Self::NonConvergence {
97 iterations, residual
98 } => write!(f, "not converged after {iterations} iterations; residual {residual}"),
99 Self::Operator(s) => write!(f, "linear operator error: {s}"),
100 }
101 }
102}
103impl Error for SolverError {
104}