loeres/error.rs
1//! Allocation-free error topology for `loeres` (RFC 003).
2//!
3//! [`SolverError`] is the canonical, copyable, allocation-free error returned by
4//! every fallible core API. It implements `Debug` but deliberately **not**
5//! `Display` or `core::error::Error`: formatting support encourages string
6//! paths and inflates device binaries, so human-facing presentation belongs to
7//! `loeres-cluster` or host-side tooling, not the edge baseline.
8//!
9//! Non-convergence is **not** an error here. A bounded loop that reaches its
10//! iteration cap returns `Ok` with `SolveStatus::NotConverged` (RFC 014).
11
12/// Allocation-free, copyable error categories shared across every Loeres crate.
13///
14/// Marked `#[non_exhaustive]`: downstream `match`es must include a wildcard arm,
15/// so future solvers can add categories without a breaking change.
16#[non_exhaustive]
17#[derive(Copy, Clone, Debug, Eq, PartialEq)]
18pub enum SolverError {
19 /// Two shapes that had to agree did not. Payloads are the mismatched extents.
20 DimensionMismatch {
21 /// Left-hand extent.
22 lhs: u32,
23 /// Right-hand extent.
24 rhs: u32,
25 },
26 /// A single dimension was itself invalid (e.g. zero where positive required).
27 InvalidDimension,
28 /// Input violated a declared domain or problem contract.
29 InvalidInput,
30 /// A floating-like input was NaN or infinite.
31 NonFiniteInput,
32 /// The problem is well-formed but unsupported by the selected solver/profile.
33 UnsupportedProblemStructure,
34 /// The system matrix was singular under the selected solver.
35 SingularMatrix,
36 /// Conditioning exceeded the solver's declared stability threshold.
37 IllConditioned,
38 /// An operation would leave the solver's valid numerical domain โ division by
39 /// zero, square root of a negative, or logarithm of a non-positive value.
40 NumericalDomain,
41 /// A checked scalar or storage operation overflowed.
42 Overflow,
43 /// The caller-provided workspace cannot hold the required scratch state.
44 WorkspaceTooSmall,
45 /// A cluster cancellation token was observed.
46 Cancelled,
47 /// An optional backend was unavailable.
48 BackendUnavailable,
49 /// A library invariant was violated โ a bug, surfaced as an error rather than
50 /// a panic so device callers can fail closed.
51 InternalInvariantViolation,
52}
53
54// RFC 003 ยง3.3: a bloated error bloats every `Result<T, SolverError>` return
55// path and raises device stack pressure. Keep it small, forever.
56const _: () = assert!(core::mem::size_of::<SolverError>() <= 16);
57
58impl SolverError {
59 /// True for malformed *caller input* โ bad dimensions, or non-finite /
60 /// otherwise invalid values supplied to a public entry point.
61 #[inline]
62 #[must_use]
63 pub const fn is_input_error(self) -> bool {
64 matches!(
65 self,
66 Self::DimensionMismatch { .. }
67 | Self::InvalidDimension
68 | Self::InvalidInput
69 | Self::NonFiniteInput
70 )
71 }
72
73 /// True for numerical failures encountered while solving.
74 #[inline]
75 #[must_use]
76 pub const fn is_numerical_error(self) -> bool {
77 matches!(
78 self,
79 Self::SingularMatrix | Self::IllConditioned | Self::NumericalDomain | Self::Overflow
80 )
81 }
82
83 /// True for resource / availability failures (workspace, cancellation, backend).
84 #[inline]
85 #[must_use]
86 pub const fn is_resource_error(self) -> bool {
87 matches!(
88 self,
89 Self::WorkspaceTooSmall | Self::Cancelled | Self::BackendUnavailable
90 )
91 }
92}
93
94/// Map an error to a stable, allocation-free `snake_case` identifier.
95///
96/// Intended for host-side logging and diagnostics. The mapping is part of the
97/// public contract and is pinned by tests; renaming a code is a reviewed change.
98#[inline]
99#[must_use]
100pub const fn error_code_to_str(err: SolverError) -> &'static str {
101 match err {
102 SolverError::DimensionMismatch { .. } => "dimension_mismatch",
103 SolverError::InvalidDimension => "invalid_dimension",
104 SolverError::InvalidInput => "invalid_input",
105 SolverError::NonFiniteInput => "non_finite_input",
106 SolverError::UnsupportedProblemStructure => "unsupported_problem_structure",
107 SolverError::SingularMatrix => "singular_matrix",
108 SolverError::IllConditioned => "ill_conditioned",
109 SolverError::NumericalDomain => "numerical_domain",
110 SolverError::Overflow => "overflow",
111 SolverError::WorkspaceTooSmall => "workspace_too_small",
112 SolverError::Cancelled => "cancelled",
113 SolverError::BackendUnavailable => "backend_unavailable",
114 SolverError::InternalInvariantViolation => "internal_invariant_violation",
115 }
116}
117
118#[cfg(test)]
119mod tests;