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