Skip to main content

nabled_linalg/
lib.rs

1//! Ndarray-native linear algebra domains for the `nabled` workspace.
2//!
3//! `nabled-linalg` provides decomposition routines, dense/sparse kernels,
4//! vector/tensor primitives, and matrix-function algorithms over ndarray data.
5//!
6//! # Feature Flags
7//!
8//! 1. `blas`: enables BLAS acceleration through `ndarray/blas`.
9//! 2. `openblas-system`: enables provider-backed `LAPACK` paths via system `OpenBLAS`.
10//! 3. `openblas-static`: enables provider-backed `LAPACK` paths via statically linked `OpenBLAS`.
11//! 4. `netlib-system`: enables provider-backed `LAPACK` paths via system `Netlib` `LAPACK`.
12//! 5. `netlib-static`: enables provider-backed `LAPACK` paths via statically linked `Netlib`
13//!    `LAPACK`.
14//! 6. `magma-system`: enables CUDA-backed MAGMA provider paths for supported decomposition domains.
15//! 7. `accelerator-rayon`: enables selected parallel CPU kernels.
16//! 8. `accelerator-wgpu`: enables bounded GPU (`f32`/`f64`) kernel paths.
17//!
18//! # Execution Model
19//!
20//! 1. `Provider`: decomposition implementation source (internal or selected LAPACK provider).
21//! 2. `Backend`: operation-kernel execution target (`CpuBackend`, `GpuBackend`).
22//! 3. `Kernel`: operation-family contract (for example matmat, sparse matvec, tensor contraction).
23//!
24//! Provider selection and backend selection are orthogonal, compile-time concerns.
25//! Public APIs remain ndarray-native and backend/provider agnostic.
26//!
27//! # Example
28//!
29//! ```rust
30//! use ndarray::arr2;
31//! use nabled_linalg::svd;
32//!
33//! let a = arr2(&[[1.0_f64, 2.0], [3.0, 4.0]]);
34//! let decomposition = svd::decompose(&a)?;
35//! assert_eq!(decomposition.singular_values.len(), 2);
36//! # Ok::<(), nabled_linalg::svd::SVDError>(())
37//! ```
38
39use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
40
41use crate::accelerator::backends::AcceleratorError;
42use crate::cholesky::CholeskyError;
43use crate::eigen::EigenError;
44use crate::lu::LUError;
45use crate::matrix::MatrixError;
46use crate::matrix_functions::MatrixFunctionError;
47use crate::orthogonalization::OrthogonalizationError;
48use crate::polar::PolarError;
49use crate::qr::QRError;
50use crate::schur::SchurError;
51use crate::sparse::SparseError;
52use crate::svd::SVDError;
53use crate::sylvester::SylvesterError;
54use crate::tensor::TensorError;
55use crate::triangular::TriangularError;
56use crate::vector::VectorError;
57
58pub mod accelerator;
59pub mod batched;
60mod internal;
61#[cfg(all(test, feature = "magma-system"))]
62mod magma_verification;
63mod provider;
64
65pub mod cholesky;
66pub mod eigen;
67pub mod lu;
68pub mod matrix;
69pub mod matrix_functions;
70pub mod orthogonalization;
71pub mod polar;
72pub mod qr;
73pub mod schur;
74pub mod sparse;
75pub mod svd;
76pub mod sylvester;
77pub mod tensor;
78pub mod triangular;
79pub mod vector;
80
81impl IntoNabledError for AcceleratorError {
82    fn into_nabled_error(self) -> NabledError {
83        match self {
84            AcceleratorError::UnsupportedBackend(kind) => {
85                NabledError::Other(format!("backend {kind:?} is not currently available"))
86            }
87            AcceleratorError::InvalidChunkSize => {
88                NabledError::InvalidInput("chunk size must be greater than zero".to_string())
89            }
90            AcceleratorError::DimensionMismatch => {
91                NabledError::Shape(ShapeError::DimensionMismatch)
92            }
93            AcceleratorError::FeatureNotEnabled => {
94                NabledError::Other("requested accelerator feature is not enabled".to_string())
95            }
96            AcceleratorError::DeviceUnavailable => {
97                NabledError::Other("no suitable GPU device is available".to_string())
98            }
99            AcceleratorError::KernelExecutionFailed => {
100                NabledError::Other("GPU kernel execution failed".to_string())
101            }
102        }
103    }
104}
105
106impl IntoNabledError for CholeskyError {
107    fn into_nabled_error(self) -> NabledError {
108        match self {
109            CholeskyError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
110            CholeskyError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
111            CholeskyError::NotPositiveDefinite => NabledError::NotPositiveDefinite,
112            CholeskyError::InvalidInput(message) => NabledError::InvalidInput(message),
113            CholeskyError::NumericalInstability => NabledError::NumericalInstability,
114        }
115    }
116}
117
118impl IntoNabledError for EigenError {
119    fn into_nabled_error(self) -> NabledError {
120        match self {
121            EigenError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
122            EigenError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
123            EigenError::NotSymmetric => NabledError::NotSymmetric,
124            EigenError::InvalidDimensions => NabledError::Shape(ShapeError::DimensionMismatch),
125            EigenError::NotPositiveDefinite => NabledError::NotPositiveDefinite,
126            EigenError::ConvergenceFailed => NabledError::ConvergenceFailed,
127            EigenError::NumericalInstability => NabledError::NumericalInstability,
128        }
129    }
130}
131
132impl IntoNabledError for LUError {
133    fn into_nabled_error(self) -> NabledError {
134        match self {
135            LUError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
136            LUError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
137            LUError::SingularMatrix => NabledError::SingularMatrix,
138            LUError::ConvergenceFailed => NabledError::ConvergenceFailed,
139            LUError::InvalidInput(message) => NabledError::InvalidInput(message),
140            LUError::NumericalInstability => NabledError::NumericalInstability,
141        }
142    }
143}
144
145impl IntoNabledError for MatrixError {
146    fn into_nabled_error(self) -> NabledError {
147        match self {
148            MatrixError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
149            MatrixError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
150        }
151    }
152}
153
154impl IntoNabledError for MatrixFunctionError {
155    fn into_nabled_error(self) -> NabledError {
156        match self {
157            MatrixFunctionError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
158            MatrixFunctionError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
159            MatrixFunctionError::NotSymmetric => NabledError::NotSymmetric,
160            MatrixFunctionError::NotPositiveDefinite => NabledError::NotPositiveDefinite,
161            MatrixFunctionError::ConvergenceFailed => NabledError::ConvergenceFailed,
162            MatrixFunctionError::InvalidInput(message) => NabledError::InvalidInput(message),
163        }
164    }
165}
166
167impl IntoNabledError for OrthogonalizationError {
168    fn into_nabled_error(self) -> NabledError {
169        match self {
170            OrthogonalizationError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
171            OrthogonalizationError::NumericalInstability => NabledError::NumericalInstability,
172        }
173    }
174}
175
176impl IntoNabledError for PolarError {
177    fn into_nabled_error(self) -> NabledError {
178        match self {
179            PolarError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
180            PolarError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
181            PolarError::DecompositionFailed => NabledError::ConvergenceFailed,
182            PolarError::NumericalInstability => NabledError::NumericalInstability,
183        }
184    }
185}
186
187impl IntoNabledError for QRError {
188    fn into_nabled_error(self) -> NabledError {
189        match self {
190            QRError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
191            QRError::SingularMatrix => NabledError::SingularMatrix,
192            QRError::ConvergenceFailed => NabledError::ConvergenceFailed,
193            QRError::InvalidDimensions(message) | QRError::InvalidInput(message) => {
194                NabledError::InvalidInput(message)
195            }
196            QRError::NumericalInstability => NabledError::NumericalInstability,
197        }
198    }
199}
200
201impl IntoNabledError for SchurError {
202    fn into_nabled_error(self) -> NabledError {
203        match self {
204            SchurError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
205            SchurError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
206            SchurError::ConvergenceFailed => NabledError::ConvergenceFailed,
207            SchurError::NumericalInstability => NabledError::NumericalInstability,
208            SchurError::InvalidInput(message) => NabledError::InvalidInput(message),
209        }
210    }
211}
212
213impl IntoNabledError for SparseError {
214    fn into_nabled_error(self) -> NabledError {
215        match self {
216            SparseError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
217            SparseError::InvalidStructure => {
218                NabledError::InvalidInput("invalid sparse structure".to_string())
219            }
220            SparseError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
221            SparseError::SingularMatrix => NabledError::SingularMatrix,
222            SparseError::MaxIterationsExceeded => NabledError::ConvergenceFailed,
223        }
224    }
225}
226
227impl IntoNabledError for SVDError {
228    fn into_nabled_error(self) -> NabledError {
229        match self {
230            SVDError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
231            SVDError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
232            SVDError::ConvergenceFailed => NabledError::ConvergenceFailed,
233            SVDError::InvalidInput(message) => NabledError::InvalidInput(message),
234        }
235    }
236}
237
238impl IntoNabledError for SylvesterError {
239    fn into_nabled_error(self) -> NabledError {
240        match self {
241            SylvesterError::EmptyMatrix => NabledError::Shape(ShapeError::EmptyInput),
242            SylvesterError::NotSquare => NabledError::Shape(ShapeError::NotSquare),
243            SylvesterError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
244            SylvesterError::SingularSystem => NabledError::SingularMatrix,
245            SylvesterError::InvalidInput(message) => NabledError::InvalidInput(message),
246        }
247    }
248}
249
250impl IntoNabledError for TriangularError {
251    fn into_nabled_error(self) -> NabledError {
252        match self {
253            TriangularError::Shape(error) => NabledError::Shape(error),
254            TriangularError::Singular => NabledError::SingularMatrix,
255        }
256    }
257}
258
259impl IntoNabledError for TensorError {
260    fn into_nabled_error(self) -> NabledError {
261        match self {
262            TensorError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
263            TensorError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
264        }
265    }
266}
267
268impl IntoNabledError for VectorError {
269    fn into_nabled_error(self) -> NabledError {
270        match self {
271            VectorError::EmptyInput => NabledError::Shape(ShapeError::EmptyInput),
272            VectorError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
273            VectorError::ZeroNorm => NabledError::InvalidInput(
274                "cosine similarity is undefined for zero-norm vectors".to_string(),
275            ),
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
283
284    use super::*;
285    use crate::accelerator::backends::BackendKind;
286
287    #[test]
288    fn linalg_errors_map_to_shared_taxonomy() {
289        assert!(matches!(
290            CholeskyError::EmptyMatrix.into_nabled_error(),
291            NabledError::Shape(ShapeError::EmptyInput)
292        ));
293        assert!(matches!(
294            CholeskyError::NotSquare.into_nabled_error(),
295            NabledError::Shape(ShapeError::NotSquare)
296        ));
297        assert!(matches!(
298            CholeskyError::NotPositiveDefinite.into_nabled_error(),
299            NabledError::NotPositiveDefinite
300        ));
301        assert!(matches!(
302            CholeskyError::NumericalInstability.into_nabled_error(),
303            NabledError::NumericalInstability
304        ));
305        assert!(matches!(
306            CholeskyError::InvalidInput("x".to_string()).into_nabled_error(),
307            NabledError::InvalidInput(_)
308        ));
309
310        assert!(matches!(EigenError::NotSymmetric.into_nabled_error(), NabledError::NotSymmetric));
311        assert!(matches!(
312            EigenError::InvalidDimensions.into_nabled_error(),
313            NabledError::Shape(ShapeError::DimensionMismatch)
314        ));
315
316        assert!(matches!(LUError::SingularMatrix.into_nabled_error(), NabledError::SingularMatrix));
317        assert!(matches!(
318            MatrixError::EmptyInput.into_nabled_error(),
319            NabledError::Shape(ShapeError::EmptyInput)
320        ));
321        assert!(matches!(
322            MatrixError::DimensionMismatch.into_nabled_error(),
323            NabledError::Shape(ShapeError::DimensionMismatch)
324        ));
325
326        assert!(matches!(
327            MatrixFunctionError::ConvergenceFailed.into_nabled_error(),
328            NabledError::ConvergenceFailed
329        ));
330
331        assert!(matches!(
332            OrthogonalizationError::NumericalInstability.into_nabled_error(),
333            NabledError::NumericalInstability
334        ));
335
336        assert!(matches!(
337            PolarError::DecompositionFailed.into_nabled_error(),
338            NabledError::ConvergenceFailed
339        ));
340
341        assert!(matches!(QRError::SingularMatrix.into_nabled_error(), NabledError::SingularMatrix));
342        assert!(matches!(
343            QRError::InvalidDimensions("x".to_string()).into_nabled_error(),
344            NabledError::InvalidInput(_)
345        ));
346        assert!(matches!(
347            QRError::InvalidInput("y".to_string()).into_nabled_error(),
348            NabledError::InvalidInput(_)
349        ));
350    }
351
352    #[test]
353    fn linalg_errors_map_to_shared_taxonomy_additional_domains() {
354        assert!(matches!(
355            SchurError::InvalidInput("x".to_string()).into_nabled_error(),
356            NabledError::InvalidInput(_)
357        ));
358
359        assert!(matches!(
360            SparseError::InvalidStructure.into_nabled_error(),
361            NabledError::InvalidInput(_)
362        ));
363        assert!(matches!(
364            SparseError::DimensionMismatch.into_nabled_error(),
365            NabledError::Shape(ShapeError::DimensionMismatch)
366        ));
367        assert!(matches!(
368            SparseError::SingularMatrix.into_nabled_error(),
369            NabledError::SingularMatrix
370        ));
371        assert!(matches!(
372            SparseError::MaxIterationsExceeded.into_nabled_error(),
373            NabledError::ConvergenceFailed
374        ));
375
376        assert!(matches!(
377            SVDError::ConvergenceFailed.into_nabled_error(),
378            NabledError::ConvergenceFailed
379        ));
380
381        assert!(matches!(
382            SylvesterError::SingularSystem.into_nabled_error(),
383            NabledError::SingularMatrix
384        ));
385
386        assert!(matches!(
387            TriangularError::Singular.into_nabled_error(),
388            NabledError::SingularMatrix
389        ));
390
391        assert!(matches!(VectorError::ZeroNorm.into_nabled_error(), NabledError::InvalidInput(_)));
392        assert!(matches!(
393            TensorError::EmptyInput.into_nabled_error(),
394            NabledError::Shape(ShapeError::EmptyInput)
395        ));
396        assert!(matches!(
397            TensorError::DimensionMismatch.into_nabled_error(),
398            NabledError::Shape(ShapeError::DimensionMismatch)
399        ));
400        assert!(matches!(
401            AcceleratorError::UnsupportedBackend(BackendKind::Gpu).into_nabled_error(),
402            NabledError::Other(_)
403        ));
404        assert!(matches!(
405            AcceleratorError::InvalidChunkSize.into_nabled_error(),
406            NabledError::InvalidInput(_)
407        ));
408        assert!(matches!(
409            AcceleratorError::DimensionMismatch.into_nabled_error(),
410            NabledError::Shape(ShapeError::DimensionMismatch)
411        ));
412        assert!(matches!(
413            AcceleratorError::FeatureNotEnabled.into_nabled_error(),
414            NabledError::Other(_)
415        ));
416    }
417
418    #[test]
419    fn linalg_error_mapping_covers_remaining_variants_part_1() {
420        assert!(matches!(
421            EigenError::EmptyMatrix.into_nabled_error(),
422            NabledError::Shape(ShapeError::EmptyInput)
423        ));
424        assert!(matches!(
425            EigenError::NotSquare.into_nabled_error(),
426            NabledError::Shape(ShapeError::NotSquare)
427        ));
428        assert!(matches!(
429            EigenError::NotPositiveDefinite.into_nabled_error(),
430            NabledError::NotPositiveDefinite
431        ));
432        assert!(matches!(
433            EigenError::ConvergenceFailed.into_nabled_error(),
434            NabledError::ConvergenceFailed
435        ));
436        assert!(matches!(
437            EigenError::NumericalInstability.into_nabled_error(),
438            NabledError::NumericalInstability
439        ));
440
441        assert!(matches!(
442            LUError::EmptyMatrix.into_nabled_error(),
443            NabledError::Shape(ShapeError::EmptyInput)
444        ));
445        assert!(matches!(
446            LUError::NotSquare.into_nabled_error(),
447            NabledError::Shape(ShapeError::NotSquare)
448        ));
449        assert!(matches!(
450            LUError::InvalidInput("x".to_string()).into_nabled_error(),
451            NabledError::InvalidInput(_)
452        ));
453        assert!(matches!(
454            LUError::ConvergenceFailed.into_nabled_error(),
455            NabledError::ConvergenceFailed
456        ));
457        assert!(matches!(
458            LUError::NumericalInstability.into_nabled_error(),
459            NabledError::NumericalInstability
460        ));
461
462        assert!(matches!(
463            QRError::EmptyMatrix.into_nabled_error(),
464            NabledError::Shape(ShapeError::EmptyInput)
465        ));
466        assert!(matches!(
467            QRError::ConvergenceFailed.into_nabled_error(),
468            NabledError::ConvergenceFailed
469        ));
470        assert!(matches!(
471            QRError::NumericalInstability.into_nabled_error(),
472            NabledError::NumericalInstability
473        ));
474    }
475
476    #[test]
477    fn linalg_error_mapping_covers_remaining_variants_part_2() {
478        assert!(matches!(
479            MatrixFunctionError::EmptyMatrix.into_nabled_error(),
480            NabledError::Shape(ShapeError::EmptyInput)
481        ));
482        assert!(matches!(
483            MatrixFunctionError::NotSquare.into_nabled_error(),
484            NabledError::Shape(ShapeError::NotSquare)
485        ));
486        assert!(matches!(
487            MatrixFunctionError::NotSymmetric.into_nabled_error(),
488            NabledError::NotSymmetric
489        ));
490        assert!(matches!(
491            MatrixFunctionError::NotPositiveDefinite.into_nabled_error(),
492            NabledError::NotPositiveDefinite
493        ));
494        assert!(matches!(
495            MatrixFunctionError::InvalidInput("x".to_string()).into_nabled_error(),
496            NabledError::InvalidInput(_)
497        ));
498
499        assert!(matches!(
500            OrthogonalizationError::EmptyMatrix.into_nabled_error(),
501            NabledError::Shape(ShapeError::EmptyInput)
502        ));
503
504        assert!(matches!(
505            PolarError::EmptyMatrix.into_nabled_error(),
506            NabledError::Shape(ShapeError::EmptyInput)
507        ));
508        assert!(matches!(
509            PolarError::NotSquare.into_nabled_error(),
510            NabledError::Shape(ShapeError::NotSquare)
511        ));
512        assert!(matches!(
513            PolarError::NumericalInstability.into_nabled_error(),
514            NabledError::NumericalInstability
515        ));
516
517        assert!(matches!(
518            SchurError::EmptyMatrix.into_nabled_error(),
519            NabledError::Shape(ShapeError::EmptyInput)
520        ));
521        assert!(matches!(
522            SchurError::NotSquare.into_nabled_error(),
523            NabledError::Shape(ShapeError::NotSquare)
524        ));
525        assert!(matches!(
526            SchurError::ConvergenceFailed.into_nabled_error(),
527            NabledError::ConvergenceFailed
528        ));
529        assert!(matches!(
530            SchurError::NumericalInstability.into_nabled_error(),
531            NabledError::NumericalInstability
532        ));
533
534        assert!(matches!(
535            SparseError::EmptyInput.into_nabled_error(),
536            NabledError::Shape(ShapeError::EmptyInput)
537        ));
538    }
539
540    #[test]
541    fn linalg_error_mapping_covers_remaining_variants_part_3() {
542        assert!(matches!(
543            SVDError::EmptyMatrix.into_nabled_error(),
544            NabledError::Shape(ShapeError::EmptyInput)
545        ));
546        assert!(matches!(
547            SVDError::NotSquare.into_nabled_error(),
548            NabledError::Shape(ShapeError::NotSquare)
549        ));
550        assert!(matches!(
551            SVDError::InvalidInput("x".to_string()).into_nabled_error(),
552            NabledError::InvalidInput(_)
553        ));
554
555        assert!(matches!(
556            SylvesterError::EmptyMatrix.into_nabled_error(),
557            NabledError::Shape(ShapeError::EmptyInput)
558        ));
559        assert!(matches!(
560            SylvesterError::NotSquare.into_nabled_error(),
561            NabledError::Shape(ShapeError::NotSquare)
562        ));
563        assert!(matches!(
564            SylvesterError::DimensionMismatch.into_nabled_error(),
565            NabledError::Shape(ShapeError::DimensionMismatch)
566        ));
567        assert!(matches!(
568            SylvesterError::InvalidInput("x".to_string()).into_nabled_error(),
569            NabledError::InvalidInput(_)
570        ));
571
572        assert!(matches!(
573            TriangularError::Shape(ShapeError::NotSquare).into_nabled_error(),
574            NabledError::Shape(ShapeError::NotSquare)
575        ));
576
577        assert!(matches!(
578            VectorError::EmptyInput.into_nabled_error(),
579            NabledError::Shape(ShapeError::EmptyInput)
580        ));
581        assert!(matches!(
582            VectorError::DimensionMismatch.into_nabled_error(),
583            NabledError::Shape(ShapeError::DimensionMismatch)
584        ));
585    }
586}