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