Skip to main content

nabled_ml/
lib.rs

1//! Ndarray-native ML-oriented numerical domains for the `nabled` workspace.
2//!
3//! `nabled-ml` composes `nabled-linalg` kernels into higher-level ML/statistical
4//! routines over ndarray vectors and matrices.
5//!
6//! # Included Domains
7//!
8//! 1. [`iterative`]: iterative linear-system solvers.
9//! 2. [`optimization`]: first/second-order optimization routines.
10//! 3. [`jacobian`]: numerical Jacobian/gradient/Hessian estimators.
11//! 4. [`pca`]: principal component analysis and transforms.
12//! 5. [`regression`]: linear regression routines.
13//! 6. [`stats`]: covariance/correlation/centering utilities.
14//!
15//! # Feature Flags
16//!
17//! 1. `blas`: enables BLAS acceleration via `nabled-linalg/blas`.
18//! 2. `magma-system`: enables NVIDIA MAGMA provider-backed decomposition paths where used through
19//!    `nabled-linalg`.
20//! 3. `lapack-provider`: enables provider-dispatched decomposition paths.
21//! 4. `openblas-system`: enables provider-backed `LAPACK` paths via system `OpenBLAS`.
22//! 5. `openblas-static`: enables provider-backed `LAPACK` paths via statically linked `OpenBLAS`.
23//! 6. `netlib-system`: enables provider-backed `LAPACK` paths via system `Netlib` `LAPACK`.
24//! 7. `netlib-static`: enables provider-backed `LAPACK` paths via statically linked `Netlib`
25//!    `LAPACK`.
26//!
27//! # Example
28//!
29//! ```rust
30//! use ndarray::{arr1, arr2};
31//! use nabled_ml::regression;
32//!
33//! let x = arr2(&[[1.0_f64, 1.0], [1.0, 2.0], [1.0, 3.0]]);
34//! let y = arr1(&[1.0_f64, 2.0, 3.0]);
35//! let model = regression::linear_regression(&x, &y)?;
36//! assert_eq!(model.coefficients.len(), 2);
37//! # Ok::<(), nabled_ml::regression::RegressionError>(())
38//! ```
39
40use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
41
42use crate::iterative::IterativeError;
43use crate::jacobian::JacobianError;
44use crate::optimization::OptimizationError;
45use crate::pca::PCAError;
46use crate::regression::RegressionError;
47use crate::stats::StatsError;
48
49pub mod iterative;
50pub mod jacobian;
51pub mod optimization;
52pub mod pca;
53pub mod regression;
54pub mod stats;
55
56impl IntoNabledError for IterativeError {
57    fn into_nabled_error(self) -> NabledError {
58        match self {
59            IterativeError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
60            IterativeError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
61            IterativeError::MaxIterationsExceeded | IterativeError::Breakdown => {
62                NabledError::ConvergenceFailed
63            }
64            IterativeError::NotPositiveDefinite => NabledError::NotPositiveDefinite,
65        }
66    }
67}
68
69impl IntoNabledError for JacobianError {
70    fn into_nabled_error(self) -> NabledError {
71        match self {
72            JacobianError::FunctionError(message) => {
73                NabledError::Other(format!("function error: {message}"))
74            }
75            JacobianError::InvalidDimensions(message) => NabledError::InvalidInput(message),
76            JacobianError::InvalidStepSize => {
77                NabledError::InvalidInput("invalid step size".to_string())
78            }
79            JacobianError::ConvergenceFailed => NabledError::ConvergenceFailed,
80            JacobianError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
81            JacobianError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
82        }
83    }
84}
85
86impl IntoNabledError for OptimizationError {
87    fn into_nabled_error(self) -> NabledError {
88        match self {
89            OptimizationError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
90            OptimizationError::DimensionMismatch => {
91                NabledError::Shape(ShapeError::DimensionMismatch)
92            }
93            OptimizationError::NonFiniteInput => NabledError::NumericalInstability,
94            OptimizationError::InvalidConfig => {
95                NabledError::InvalidInput("invalid optimizer configuration".to_string())
96            }
97            OptimizationError::MaxIterationsExceeded => NabledError::ConvergenceFailed,
98        }
99    }
100}
101
102impl IntoNabledError for PCAError {
103    fn into_nabled_error(self) -> NabledError {
104        match self {
105            PCAError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
106            PCAError::InvalidInput(message) => NabledError::InvalidInput(message),
107            PCAError::DecompositionFailed => NabledError::ConvergenceFailed,
108        }
109    }
110}
111
112impl IntoNabledError for RegressionError {
113    fn into_nabled_error(self) -> NabledError {
114        match self {
115            RegressionError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
116            RegressionError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
117            RegressionError::Singular => NabledError::SingularMatrix,
118            RegressionError::InvalidInput(message) => NabledError::InvalidInput(message),
119        }
120    }
121}
122
123impl IntoNabledError for StatsError {
124    fn into_nabled_error(self) -> NabledError {
125        match self {
126            StatsError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
127            StatsError::InsufficientSamples => {
128                NabledError::InvalidInput("at least two observations are required".to_string())
129            }
130            StatsError::InvalidInput(message) => NabledError::InvalidInput(message),
131            StatsError::NumericalInstability => NabledError::NumericalInstability,
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
139
140    use super::*;
141
142    #[test]
143    fn ml_errors_map_to_shared_taxonomy() {
144        assert!(matches!(
145            IterativeError::EmptyMatrix.into_nabled_error(),
146            NabledError::Shape(ShapeError::EmptyInput)
147        ));
148        assert!(matches!(
149            IterativeError::DimensionMismatch.into_nabled_error(),
150            NabledError::Shape(ShapeError::DimensionMismatch)
151        ));
152        assert!(matches!(
153            IterativeError::MaxIterationsExceeded.into_nabled_error(),
154            NabledError::ConvergenceFailed
155        ));
156        assert!(matches!(
157            IterativeError::Breakdown.into_nabled_error(),
158            NabledError::ConvergenceFailed
159        ));
160        assert!(matches!(
161            IterativeError::NotPositiveDefinite.into_nabled_error(),
162            NabledError::NotPositiveDefinite
163        ));
164
165        assert!(matches!(
166            JacobianError::FunctionError("x".to_string()).into_nabled_error(),
167            NabledError::Other(_)
168        ));
169        assert!(matches!(
170            JacobianError::InvalidDimensions("x".to_string()).into_nabled_error(),
171            NabledError::InvalidInput(_)
172        ));
173        assert!(matches!(
174            JacobianError::InvalidStepSize.into_nabled_error(),
175            NabledError::InvalidInput(_)
176        ));
177        assert!(matches!(
178            JacobianError::ConvergenceFailed.into_nabled_error(),
179            NabledError::ConvergenceFailed
180        ));
181        assert!(matches!(
182            JacobianError::EmptyInput.into_nabled_error(),
183            NabledError::Shape(ShapeError::EmptyInput)
184        ));
185        assert!(matches!(
186            JacobianError::DimensionMismatch.into_nabled_error(),
187            NabledError::Shape(ShapeError::DimensionMismatch)
188        ));
189
190        assert!(matches!(
191            OptimizationError::NonFiniteInput.into_nabled_error(),
192            NabledError::NumericalInstability
193        ));
194        assert!(matches!(
195            OptimizationError::InvalidConfig.into_nabled_error(),
196            NabledError::InvalidInput(_)
197        ));
198        assert!(matches!(
199            OptimizationError::MaxIterationsExceeded.into_nabled_error(),
200            NabledError::ConvergenceFailed
201        ));
202
203        assert!(matches!(
204            PCAError::DecompositionFailed.into_nabled_error(),
205            NabledError::ConvergenceFailed
206        ));
207        assert!(matches!(
208            PCAError::InvalidInput("x".to_string()).into_nabled_error(),
209            NabledError::InvalidInput(_)
210        ));
211
212        assert!(matches!(
213            RegressionError::Singular.into_nabled_error(),
214            NabledError::SingularMatrix
215        ));
216
217        assert!(matches!(
218            StatsError::EmptyMatrix.into_nabled_error(),
219            NabledError::Shape(ShapeError::EmptyInput)
220        ));
221        assert!(matches!(
222            StatsError::InsufficientSamples.into_nabled_error(),
223            NabledError::InvalidInput(_)
224        ));
225        assert!(matches!(
226            StatsError::NumericalInstability.into_nabled_error(),
227            NabledError::NumericalInstability
228        ));
229    }
230}