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