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}