Skip to main content

mdarray_linalg_faer/
lib.rs

1//! # mdarray_linalg_faer
2//!
3//! [faer](https://crates.io/crates/faer) backend for [`mdarray_linalg`].
4//!
5//! This crate provides the [`Faer`] struct that implements the linear algebra traits
6//! defined by [`mdarray_linalg`], delegating computations to the pure-Rust `faer` library.
7//! Unlike the BLAS/LAPACK backends, `faer` does **not** require a system BLAS installation.
8//!
9//! Backend implementation modules are private.  Use [`Faer`] together with the
10//! operation traits from `mdarray_linalg::prelude::*`.
11//!
12//! ## Scope
13//!
14//! The Faer backend is the most complete backend and covers:
15//!
16//! - **Level 1** — vector operations: `dot`, `dotc`, `norm2`, `norm1`, `add_to_scaled`
17//! - **Level 2** — matrix-vector & outer product: `matvec`, `outer`
18//! - **Level 3** — matrix multiplication: `matmul`
19//! - **Tensor contraction** — `contract_all`, `contract_n`, `contract_pairs`, `contract`
20//! - **Eigenvalue decomposition** — `eig`, `eig_full`, `eig_values`, `eigh`
21//! - **Schur decomposition** — `schur`, `schur_complex`
22//! - **SVD** — `svd`, `svd_thin`, `svd_s`
23//! - **LU decomposition** — `lu`, `det`, `inv`
24//! - **Cholesky decomposition** — `cholesky`
25//! - **QR decomposition** — `qr`
26//! - **Linear system solving** — `solve`
27//!
28//! ## Setup
29//!
30//! Add the dependencies to your project:
31//!
32//! ```bash
33//! cargo add mdarray mdarray-linalg mdarray-linalg-faer
34//! ```
35//!
36//! > **Note:** No BLAS/LAPACK linking is required — `faer` is a pure-Rust crate.
37//!
38//! ## Example
39//!
40//! All operations are accessed through the [`Faer`] backend via the traits from
41//! `mdarray_linalg::prelude::*`:
42//!
43//! ```rust
44//! use mdarray::array;
45//! use mdarray_linalg::prelude::*;
46//! use mdarray_linalg::eig::EigDecomp;
47//! use mdarray_linalg::svd::SVDDecomp;
48//! use mdarray_linalg_faer::Faer;
49//!
50//! // ----- Matrix multiplication (Level 3) -----
51//! let a = array![[1., 2.], [3., 4.]];
52//! let b = array![[5., 6.], [7., 8.]];
53//!
54//! let c = Faer::default().matmul(&a, &b).eval();
55//! assert_eq!(c, array![[19., 22.], [43., 50.]]);
56//!
57//! // ----- Eigenvalue decomposition -----
58//! let mut a = array![[1., 2.], [3., 4.]];
59//! let EigDecomp {
60//!     eigenvalues: lambda,
61//!     right_eigenvectors,
62//!     ..
63//! } = Faer::default().eig(&mut a.clone()).expect("Eigenvalue decomposition failed");
64//!
65//! println!("Eigenvalues: {:?}", lambda);
66//! if let Some(v) = right_eigenvectors {
67//!     println!("Right eigenvectors: {:?}", v);
68//! }
69//!
70//! // ----- SVD -----
71//! let mut a = array![[1., 2.], [3., 4.]];
72//! let SVDDecomp { s, u, vt } = Faer::default().svd_thin(&mut a).expect("SVD failed");
73//! println!("Singular values: {:?}", s);
74//!
75//! // ----- QR decomposition -----
76//! let mut a = array![[12., -51., 4.], [6., 167., -68.], [-4., 24., -41.]];
77//! let (q, r) = Faer::default().qr(&mut a);
78//! println!("Q: {:?}", q);
79//! println!("R: {:?}", r);
80//!
81//! // ----- Tensor contraction -----
82//! let t1 = array![[1., 2.], [3., 4.]].into_dyn();
83//! let t2 = array![[5., 6.], [7., 8.]].into_dyn();
84//!
85//! let scalar = Faer::default().contract_all(&t1, &t2);
86//! assert_eq!(scalar, 70.0);
87//! ```
88//!
89//! > **Note:** Decomposition routines (eig, svd, lu, etc.) **destroy the input matrix**.
90//! > Always pass a clone if you need the original data.
91//!
92//! ## Currently supported types
93//!
94//! `f32`, `f64`, `Complex<f32>`, `Complex<f64>`.
95//!
96// Keep the doc-comment blank line above: these reference definitions must start
97// a separate Markdown block from the preceding paragraph.
98#![cfg_attr(docsrs, doc = concat!(
99    "[`mdarray_linalg`]: https://docs.rs/mdarray-linalg/", env!("CARGO_PKG_VERSION"), "/mdarray_linalg/",
100))]
101#![cfg_attr(not(docsrs), doc = "\
102[`mdarray_linalg`]: ../mdarray_linalg/index.html
103")]
104
105mod eig;
106mod lu;
107mod contract;
108mod matvec;
109mod qr;
110mod solve;
111mod svd;
112
113/// Faer backend.
114///
115/// Implements the linear algebra traits from [`mdarray_linalg`] by delegating
116/// to the pure-Rust `faer` library.  This backend supports the broadest range of
117/// operations — from basic BLAS to full decompositions and tensor contractions —
118/// without requiring any system BLAS/LAPACK installation.
119#[derive(Default)]
120pub struct Faer;
121
122use mdarray::{Dim, Layout, Shape, Slice};
123
124/// Converts a `Slice<T, (_, _), L>` (from `mdarray`) into a `faer::MatRef<'a, T>`.
125/// This function **does not copy** any data.
126pub(crate) fn into_faer<'a, T, L: Layout, D0: Dim, D1: Dim>(
127    mat: &'a Slice<T, (D0, D1), L>,
128) -> faer::mat::MatRef<'a, T> {
129    let (nrows, ncols) = *mat.shape();
130    let strides = (mat.stride(0), mat.stride(1));
131
132    // SAFETY:
133    // We are constructing a MatRef from raw parts. This requires that:
134    // - `mat.as_ptr()` points to a valid matrix of size `nrows x ncols`
135    // - The given strides correctly describe the memory layout
136    unsafe {
137        faer::MatRef::from_raw_parts(
138            mat.as_ptr(),
139            nrows.size(),
140            ncols.size(),
141            strides.0,
142            strides.1,
143        )
144    }
145}
146
147/// Converts a `Slice<T, (_, _), L>` (from `mdarray`) into a `faer::MatMut<'a, T>`.
148/// This function **does not copy** any data.
149pub(crate) fn into_faer_mut<'a, T, L: Layout, D0: Dim, D1: Dim>(
150    mat: &'a mut Slice<T, (D0, D1), L>,
151) -> faer::mat::MatMut<'a, T> {
152    let (nrows, ncols) = *mat.shape();
153    let strides = (mat.stride(0), mat.stride(1));
154
155    // SAFETY:
156    // We are constructing a MatMut from raw parts. This requires that:
157    // - `mat.as_mut_ptr()` points to a valid mutable matrix of size `nrows x ncols`
158    // - The given strides correctly describe the memory layout
159    unsafe {
160        faer::MatMut::from_raw_parts_mut(
161            mat.as_mut_ptr() as *mut _,
162            nrows.size(),
163            ncols.size(),
164            strides.0,
165            strides.1,
166        )
167    }
168}
169
170/// Converts a `Slice<T, (D0, D1), L>` (from `mdarray`) into a
171/// `faer::MatMut<'a, T>` and transposes data.  This function
172/// **does not copy** any data.
173pub(crate) fn into_faer_mut_transpose<'a, T, D0: Dim, D1: Dim, L: Layout>(
174    mat: &'a mut Slice<T, (D0, D1), L>,
175) -> faer::mat::MatMut<'a, T> {
176    let matsh = *mat.shape();
177    let (nrows, ncols) = (matsh.dim(0), matsh.dim(1));
178    let strides = (mat.stride(0), mat.stride(1));
179
180    // SAFETY:
181    // We are constructing a MatMut from raw parts. This requires that:
182    // - `mat.as_mut_ptr()` points to a valid mutable matrix of size `nrows x ncols`
183    // - The given strides correctly describe the memory layout
184    unsafe {
185        faer::MatMut::from_raw_parts_mut(
186            mat.as_mut_ptr() as *mut _,
187            nrows,
188            ncols,
189            strides.1,
190            strides.0,
191        )
192    }
193}
194
195/// Converts a `Slice<T, (D0,), L>` (from `mdarray`) into a `faer::ColRef<'a, T>`.
196/// This function **does not copy** any data.
197pub(crate) fn into_faer_col<'a, T, D0: Dim, L: Layout>(
198    vec: &'a Slice<T, (D0,), L>,
199) -> faer::col::ColRef<'a, T> {
200    let n = vec.shape().dim(0);
201
202    // SAFETY:
203    // - `vec.as_ptr()` points to a valid vector with `n` elements.
204    // - `vec.stride(0)` describes the spacing between consecutive elements.
205    unsafe { faer::col::ColRef::from_raw_parts(vec.as_ptr(), n, vec.stride(0)) }
206}
207
208/// Converts a `Slice<T, (D0,), L>` (from `mdarray`) into a `faer::ColMut<'a, T>`.
209/// This function **does not copy** any data.
210pub(crate) fn into_faer_col_mut<'a, T, D0: Dim, L: Layout>(
211    vec: &'a mut Slice<T, (D0,), L>,
212) -> faer::col::ColMut<'a, T> {
213    let n = vec.shape().dim(0);
214
215    // SAFETY:
216    // - `vec.as_mut_ptr()` points to a valid mutable vector with `n` elements.
217    // - `vec.stride(0)` describes the spacing between consecutive elements.
218    unsafe { faer::col::ColMut::from_raw_parts_mut(vec.as_mut_ptr() as *mut _, n, vec.stride(0)) }
219}
220
221/// Converts a `Slice<T, (D0,), L>` (from `mdarray`) into a `faer::RowRef<'a, T>`.
222/// This function **does not copy** any data.
223pub(crate) fn into_faer_row<'a, T, D0: Dim, L: Layout>(
224    vec: &'a Slice<T, (D0,), L>,
225) -> faer::row::RowRef<'a, T> {
226    let n = vec.shape().dim(0);
227
228    // SAFETY:
229    // - `vec.as_ptr()` points to a valid vector with `n` elements.
230    // - `vec.stride(0)` describes the spacing between consecutive elements.
231    unsafe { faer::row::RowRef::from_raw_parts(vec.as_ptr(), n, vec.stride(0)) }
232}
233
234/// Converts a mutable `Slice<T, (D0,), L>` (from `mdarray`) into a
235/// `faer::diag::DiagMut<'a, T>`.
236///
237/// Internal implementation note: this is a zero-copy view used when faer wants
238/// singular values or eigenvalues as a diagonal object.  The caller must provide
239/// a vector-like slice whose pointer and stride remain valid for the returned
240/// view.
241pub(crate) fn into_faer_diag_mut<'a, T, D0: Dim, L: Layout>(
242    mat: &'a mut Slice<T, (D0,), L>,
243) -> faer::diag::DiagMut<'a, T> {
244    let n = mat.shape().dim(0);
245
246    // SAFETY:
247    // - `mat.as_mut_ptr()` must point to a buffer with at least `n` diagonal elements.
248    // - `mat.stride(1)` is used as the step between diagonal elements, assuming storage
249    //   along the first row for compatibility with LAPACK convention.
250    unsafe { faer::diag::DiagMut::from_raw_parts_mut(mat.as_mut_ptr() as *mut _, n, mat.stride(0)) }
251}