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::NumericalInstability => NabledError::NumericalInstability,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
138
139    use super::*;
140
141    #[test]
142    fn ml_errors_map_to_shared_taxonomy() {
143        assert!(matches!(
144            IterativeError::EmptyMatrix.into_nabled_error(),
145            NabledError::Shape(ShapeError::EmptyInput)
146        ));
147        assert!(matches!(
148            IterativeError::DimensionMismatch.into_nabled_error(),
149            NabledError::Shape(ShapeError::DimensionMismatch)
150        ));
151        assert!(matches!(
152            IterativeError::MaxIterationsExceeded.into_nabled_error(),
153            NabledError::ConvergenceFailed
154        ));
155        assert!(matches!(
156            IterativeError::Breakdown.into_nabled_error(),
157            NabledError::ConvergenceFailed
158        ));
159        assert!(matches!(
160            IterativeError::NotPositiveDefinite.into_nabled_error(),
161            NabledError::NotPositiveDefinite
162        ));
163
164        assert!(matches!(
165            JacobianError::FunctionError("x".to_string()).into_nabled_error(),
166            NabledError::Other(_)
167        ));
168        assert!(matches!(
169            JacobianError::InvalidDimensions("x".to_string()).into_nabled_error(),
170            NabledError::InvalidInput(_)
171        ));
172        assert!(matches!(
173            JacobianError::InvalidStepSize.into_nabled_error(),
174            NabledError::InvalidInput(_)
175        ));
176        assert!(matches!(
177            JacobianError::ConvergenceFailed.into_nabled_error(),
178            NabledError::ConvergenceFailed
179        ));
180        assert!(matches!(
181            JacobianError::EmptyInput.into_nabled_error(),
182            NabledError::Shape(ShapeError::EmptyInput)
183        ));
184        assert!(matches!(
185            JacobianError::DimensionMismatch.into_nabled_error(),
186            NabledError::Shape(ShapeError::DimensionMismatch)
187        ));
188
189        assert!(matches!(
190            OptimizationError::NonFiniteInput.into_nabled_error(),
191            NabledError::NumericalInstability
192        ));
193        assert!(matches!(
194            OptimizationError::InvalidConfig.into_nabled_error(),
195            NabledError::InvalidInput(_)
196        ));
197        assert!(matches!(
198            OptimizationError::MaxIterationsExceeded.into_nabled_error(),
199            NabledError::ConvergenceFailed
200        ));
201
202        assert!(matches!(
203            PCAError::DecompositionFailed.into_nabled_error(),
204            NabledError::ConvergenceFailed
205        ));
206        assert!(matches!(
207            PCAError::InvalidInput("x".to_string()).into_nabled_error(),
208            NabledError::InvalidInput(_)
209        ));
210
211        assert!(matches!(
212            RegressionError::Singular.into_nabled_error(),
213            NabledError::SingularMatrix
214        ));
215
216        assert!(matches!(
217            StatsError::EmptyMatrix.into_nabled_error(),
218            NabledError::Shape(ShapeError::EmptyInput)
219        ));
220        assert!(matches!(
221            StatsError::InsufficientSamples.into_nabled_error(),
222            NabledError::InvalidInput(_)
223        ));
224        assert!(matches!(
225            StatsError::NumericalInstability.into_nabled_error(),
226            NabledError::NumericalInstability
227        ));
228    }
229}