lax/
lib.rs

1//! Safe Rust wrapper for LAPACK without external dependency.
2//!
3//! [Lapack] trait
4//! ----------------
5//!
6//! This crates provides LAPACK wrapper as a traits.
7//! For example, LU decomposition of general matrices is provided like:
8//!
9//! ```ignore
10//! pub trait Lapack {
11//!     fn lu(l: MatrixLayout, a: &mut [Self]) -> Result<Pivot>;
12//! }
13//! ```
14//!
15//! see [Lapack] for detail.
16//! This trait is implemented for [f32], [f64], [c32] which is an alias to `num::Complex<f32>`,
17//! and [c64] which is an alias to `num::Complex<f64>`.
18//! You can use it like `f64::lu`:
19//!
20//! ```
21//! use lax::{Lapack, layout::MatrixLayout, Transpose};
22//!
23//! let mut a = vec![
24//!   1.0, 2.0,
25//!   3.0, 4.0
26//! ];
27//! let mut b = vec![1.0, 2.0];
28//! let layout = MatrixLayout::C { row: 2, lda: 2 };
29//! let pivot = f64::lu(layout, &mut a).unwrap();
30//! f64::solve(layout, Transpose::No, &a, &pivot, &mut b).unwrap();
31//! ```
32//!
33//! When you want to write generic algorithm for real and complex matrices,
34//! this trait can be used as a trait bound:
35//!
36//! ```
37//! use lax::{Lapack, layout::MatrixLayout, Transpose};
38//!
39//! fn solve_at_once<T: Lapack>(layout: MatrixLayout, a: &mut [T], b: &mut [T]) -> Result<(), lax::error::Error> {
40//!   let pivot = T::lu(layout, a)?;
41//!   T::solve(layout, Transpose::No, a, &pivot, b)?;
42//!   Ok(())
43//! }
44//! ```
45//!
46//! There are several similar traits as described below to keep development easy.
47//! They are merged into a single trait, [Lapack].
48//!
49//! Linear equation, Inverse matrix, Condition number
50//! --------------------------------------------------
51//!
52//! According to the property input metrix, several types of triangular decomposition are used:
53//!
54//! - [solve] module provides methods for LU-decomposition for general matrix.
55//! - [solveh] module provides methods for Bunch-Kaufman diagonal pivoting method for symmetric/Hermitian indefinite matrix.
56//! - [cholesky] module provides methods for Cholesky decomposition for symmetric/Hermitian positive dinite matrix.
57//!
58//! Eigenvalue Problem
59//! -------------------
60//!
61//! According to the property input metrix,
62//! there are several types of eigenvalue problem API
63//!
64//! - [eig] module for eigenvalue problem for general matrix.
65//! - [eig_generalized] module for generalized eigenvalue problem for general matrix.
66//! - [eigh] module for eigenvalue problem for symmetric/Hermitian matrix.
67//! - [eigh_generalized] module for generalized eigenvalue problem for symmetric/Hermitian matrix.
68//!
69//! Singular Value Decomposition
70//! -----------------------------
71//!
72//! - [svd] module for singular value decomposition (SVD) for general matrix
73//! - [svddc] module for singular value decomposition (SVD) with divided-and-conquer algorithm for general matrix
74//! - [least_squares] module for solving least square problem using SVD
75//!
76
77#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)]
78
79#[cfg(any(feature = "intel-mkl-system", feature = "intel-mkl-static"))]
80extern crate intel_mkl_src as _src;
81
82#[cfg(any(feature = "openblas-system", feature = "openblas-static"))]
83extern crate openblas_src as _src;
84
85#[cfg(any(feature = "netlib-system", feature = "netlib-static"))]
86extern crate netlib_src as _src;
87
88pub mod alloc;
89pub mod cholesky;
90pub mod eig;
91pub mod eig_generalized;
92pub mod eigh;
93pub mod eigh_generalized;
94pub mod error;
95pub mod flags;
96pub mod layout;
97pub mod least_squares;
98pub mod opnorm;
99pub mod qr;
100pub mod rcond;
101pub mod solve;
102pub mod solveh;
103pub mod svd;
104pub mod svddc;
105pub mod triangular;
106pub mod tridiagonal;
107
108pub use crate::eig_generalized::GeneralizedEigenvalue;
109
110pub use self::flags::*;
111pub use self::least_squares::LeastSquaresOwned;
112pub use self::svd::{SvdOwned, SvdRef};
113pub use self::tridiagonal::{LUFactorizedTridiagonal, Tridiagonal};
114
115use self::{alloc::*, error::*, layout::*};
116use cauchy::*;
117use std::mem::MaybeUninit;
118
119pub type Pivot = Vec<i32>;
120
121#[cfg_attr(doc, katexit::katexit)]
122/// Trait for primitive types which implements LAPACK subroutines
123pub trait Lapack: Scalar {
124    /// Compute right eigenvalue and eigenvectors for a general matrix
125    fn eig(
126        calc_v: bool,
127        l: MatrixLayout,
128        a: &mut [Self],
129    ) -> Result<(Vec<Self::Complex>, Vec<Self::Complex>)>;
130
131    /// Compute right eigenvalue and eigenvectors for a general matrix
132    fn eig_generalized(
133        calc_v: bool,
134        l: MatrixLayout,
135        a: &mut [Self],
136        b: &mut [Self],
137        thresh_opt: Option<Self::Real>,
138    ) -> Result<(
139        Vec<GeneralizedEigenvalue<Self::Complex>>,
140        Vec<Self::Complex>,
141    )>;
142
143    /// Compute right eigenvalue and eigenvectors for a symmetric or Hermitian matrix
144    fn eigh(
145        calc_eigenvec: bool,
146        layout: MatrixLayout,
147        uplo: UPLO,
148        a: &mut [Self],
149    ) -> Result<Vec<Self::Real>>;
150
151    /// Compute right eigenvalue and eigenvectors for a symmetric or Hermitian matrix
152    fn eigh_generalized(
153        calc_eigenvec: bool,
154        layout: MatrixLayout,
155        uplo: UPLO,
156        a: &mut [Self],
157        b: &mut [Self],
158    ) -> Result<Vec<Self::Real>>;
159
160    /// Execute Householder reflection as the first step of QR-decomposition
161    ///
162    /// For C-continuous array,
163    /// this will call LQ-decomposition of the transposed matrix $ A^T = LQ^T $
164    fn householder(l: MatrixLayout, a: &mut [Self]) -> Result<Vec<Self>>;
165
166    /// Reconstruct Q-matrix from Householder-reflectors
167    fn q(l: MatrixLayout, a: &mut [Self], tau: &[Self]) -> Result<()>;
168
169    /// Execute QR-decomposition at once
170    fn qr(l: MatrixLayout, a: &mut [Self]) -> Result<Vec<Self>>;
171
172    /// Compute singular-value decomposition (SVD)
173    fn svd(l: MatrixLayout, calc_u: bool, calc_vt: bool, a: &mut [Self]) -> Result<SvdOwned<Self>>;
174
175    /// Compute singular value decomposition (SVD) with divide-and-conquer algorithm
176    fn svddc(layout: MatrixLayout, jobz: JobSvd, a: &mut [Self]) -> Result<SvdOwned<Self>>;
177
178    /// Compute a vector $x$ which minimizes Euclidian norm $\| Ax - b\|$
179    /// for a given matrix $A$ and a vector $b$.
180    fn least_squares(
181        a_layout: MatrixLayout,
182        a: &mut [Self],
183        b: &mut [Self],
184    ) -> Result<LeastSquaresOwned<Self>>;
185
186    /// Solve least square problems $\argmin_X \| AX - B\|$
187    fn least_squares_nrhs(
188        a_layout: MatrixLayout,
189        a: &mut [Self],
190        b_layout: MatrixLayout,
191        b: &mut [Self],
192    ) -> Result<LeastSquaresOwned<Self>>;
193
194    /// Computes the LU decomposition of a general $m \times n$ matrix
195    /// with partial pivoting with row interchanges.
196    ///
197    /// For a given matrix $A$, LU decomposition is described as $A = PLU$ where:
198    ///
199    /// - $L$ is lower matrix
200    /// - $U$ is upper matrix
201    /// - $P$ is permutation matrix represented by [Pivot]
202    ///
203    /// This is designed as two step computation according to LAPACK API:
204    ///
205    /// 1. Factorize input matrix $A$ into $L$, $U$, and $P$.
206    /// 2. Solve linear equation $Ax = b$ by [Lapack::solve]
207    ///    or compute inverse matrix $A^{-1}$ by [Lapack::inv] using the output of LU decomposition.
208    ///
209    /// Output
210    /// -------
211    /// - $U$ and $L$ are stored in `a` after LU decomposition has succeeded.
212    /// - $P$ is returned as [Pivot]
213    ///
214    /// Error
215    /// ------
216    /// - if the matrix is singular
217    ///   - On this case, `return_code` in [Error::LapackComputationalFailure] means
218    ///     `return_code`-th diagonal element of $U$ becomes zero.
219    ///
220    fn lu(l: MatrixLayout, a: &mut [Self]) -> Result<Pivot>;
221
222    /// Compute inverse matrix $A^{-1}$ from the output of LU-decomposition
223    fn inv(l: MatrixLayout, a: &mut [Self], p: &Pivot) -> Result<()>;
224
225    /// Solve linear equations $Ax = b$ using the output of LU-decomposition
226    fn solve(l: MatrixLayout, t: Transpose, a: &[Self], p: &Pivot, b: &mut [Self]) -> Result<()>;
227
228    /// Factorize symmetric/Hermitian matrix using Bunch-Kaufman diagonal pivoting method
229    ///
230    /// For a given symmetric matrix $A$,
231    /// this method factorizes $A = U^T D U$ or $A = L D L^T$ where
232    ///
233    /// - $U$ (or $L$) are is a product of permutation and unit upper (lower) triangular matrices
234    /// - $D$ is symmetric and block diagonal with 1-by-1 and 2-by-2 diagonal blocks.
235    ///
236    /// This takes two-step approach based in LAPACK:
237    ///
238    /// 1. Factorize given matrix $A$ into upper ($U$) or lower ($L$) form with diagonal matrix $D$
239    /// 2. Then solve linear equation $Ax = b$, and/or calculate inverse matrix $A^{-1}$
240    ///
241    fn bk(l: MatrixLayout, uplo: UPLO, a: &mut [Self]) -> Result<Pivot>;
242
243    /// Compute inverse matrix $A^{-1}$ using the result of [Lapack::bk]
244    fn invh(l: MatrixLayout, uplo: UPLO, a: &mut [Self], ipiv: &Pivot) -> Result<()>;
245
246    /// Solve symmetric/Hermitian linear equation $Ax = b$ using the result of [Lapack::bk]
247    fn solveh(l: MatrixLayout, uplo: UPLO, a: &[Self], ipiv: &Pivot, b: &mut [Self]) -> Result<()>;
248
249    /// Solve symmetric/Hermitian positive-definite linear equations using Cholesky decomposition
250    ///
251    /// For a given positive definite matrix $A$,
252    /// Cholesky decomposition is described as $A = U^T U$ or $A = LL^T$ where
253    ///
254    /// - $L$ is lower matrix
255    /// - $U$ is upper matrix
256    ///
257    /// This is designed as two step computation according to LAPACK API
258    ///
259    /// 1. Factorize input matrix $A$ into $L$ or $U$
260    /// 2. Solve linear equation $Ax = b$ by [Lapack::solve_cholesky]
261    ///    or compute inverse matrix $A^{-1}$ by [Lapack::inv_cholesky]
262    ///
263    fn cholesky(l: MatrixLayout, uplo: UPLO, a: &mut [Self]) -> Result<()>;
264
265    /// Compute inverse matrix $A^{-1}$ using $U$ or $L$ calculated by [Lapack::cholesky]
266    fn inv_cholesky(l: MatrixLayout, uplo: UPLO, a: &mut [Self]) -> Result<()>;
267
268    /// Solve linear equation $Ax = b$ using $U$ or $L$ calculated by [Lapack::cholesky]
269    fn solve_cholesky(l: MatrixLayout, uplo: UPLO, a: &[Self], b: &mut [Self]) -> Result<()>;
270
271    /// Estimates the the reciprocal of the condition number of the matrix in 1-norm.
272    ///
273    /// `anorm` should be the 1-norm of the matrix `a`.
274    fn rcond(l: MatrixLayout, a: &[Self], anorm: Self::Real) -> Result<Self::Real>;
275
276    /// Compute norm of matrices
277    ///
278    /// For a $n \times m$ matrix
279    /// $$
280    /// A = \begin{pmatrix}
281    ///   a_{11} & \cdots & a_{1m} \\\\
282    ///   \vdots & \ddots & \vdots \\\\
283    ///   a_{n1} & \cdots & a_{nm}
284    /// \end{pmatrix}
285    /// $$
286    /// LAPACK can compute three types of norms:
287    ///
288    /// - Operator norm based on 1-norm for its domain linear space:
289    ///   $$
290    ///   \Vert A \Vert_1 = \sup_{\Vert x \Vert_1 = 1} \Vert Ax \Vert_1
291    ///   = \max_{1 \le j \le m } \sum_{i=1}^n |a_{ij}|
292    ///   $$
293    ///   where
294    ///   $\Vert x\Vert_1 = \sum_{j=1}^m |x_j|$
295    ///   is 1-norm for a vector $x$.
296    ///
297    /// - Operator norm based on $\infty$-norm for its domain linear space:
298    ///   $$
299    ///   \Vert A \Vert_\infty = \sup_{\Vert x \Vert_\infty = 1} \Vert Ax \Vert_\infty
300    ///   = \max_{1 \le i \le n } \sum_{j=1}^m |a_{ij}|
301    ///   $$
302    ///   where
303    ///   $\Vert x\Vert_\infty = \max_{j=1}^m |x_j|$
304    ///   is $\infty$-norm for a vector $x$.
305    ///
306    /// - Frobenious norm
307    ///   $$
308    ///   \Vert A \Vert_F = \sqrt{\mathrm{Tr} \left(AA^\dagger\right)} = \sqrt{\sum_{i=1}^n \sum_{j=1}^m |a_{ij}|^2}
309    ///   $$
310    ///
311    fn opnorm(t: NormType, l: MatrixLayout, a: &[Self]) -> Self::Real;
312
313    fn solve_triangular(
314        al: MatrixLayout,
315        bl: MatrixLayout,
316        uplo: UPLO,
317        d: Diag,
318        a: &[Self],
319        b: &mut [Self],
320    ) -> Result<()>;
321
322    /// Computes the LU factorization of a tridiagonal `m x n` matrix `a` using
323    /// partial pivoting with row interchanges.
324    fn lu_tridiagonal(a: Tridiagonal<Self>) -> Result<LUFactorizedTridiagonal<Self>>;
325
326    fn rcond_tridiagonal(lu: &LUFactorizedTridiagonal<Self>) -> Result<Self::Real>;
327
328    fn solve_tridiagonal(
329        lu: &LUFactorizedTridiagonal<Self>,
330        bl: MatrixLayout,
331        t: Transpose,
332        b: &mut [Self],
333    ) -> Result<()>;
334}
335
336macro_rules! impl_lapack {
337    ($s:ty) => {
338        impl Lapack for $s {
339            fn eig(
340                calc_v: bool,
341                l: MatrixLayout,
342                a: &mut [Self],
343            ) -> Result<(Vec<Self::Complex>, Vec<Self::Complex>)> {
344                use eig::*;
345                let work = EigWork::<$s>::new(calc_v, l)?;
346                let EigOwned { eigs, vr, vl } = work.eval(a)?;
347                Ok((eigs, vr.or(vl).unwrap_or_default()))
348            }
349
350            fn eig_generalized(
351                calc_v: bool,
352                l: MatrixLayout,
353                a: &mut [Self],
354                b: &mut [Self],
355                thresh_opt: Option<Self::Real>,
356            ) -> Result<(
357                Vec<GeneralizedEigenvalue<Self::Complex>>,
358                Vec<Self::Complex>,
359            )> {
360                use eig_generalized::*;
361                let work = EigGeneralizedWork::<$s>::new(calc_v, l)?;
362                let eig_generalized_owned = work.eval(a, b)?;
363                let eigs = eig_generalized_owned.calc_eigs(thresh_opt);
364                let vr = eig_generalized_owned.vr;
365                let vl = eig_generalized_owned.vl;
366                Ok((eigs, vr.or(vl).unwrap_or_default()))
367            }
368
369            fn eigh(
370                calc_eigenvec: bool,
371                layout: MatrixLayout,
372                uplo: UPLO,
373                a: &mut [Self],
374            ) -> Result<Vec<Self::Real>> {
375                use eigh::*;
376                let work = EighWork::<$s>::new(calc_eigenvec, layout)?;
377                work.eval(uplo, a)
378            }
379
380            fn eigh_generalized(
381                calc_eigenvec: bool,
382                layout: MatrixLayout,
383                uplo: UPLO,
384                a: &mut [Self],
385                b: &mut [Self],
386            ) -> Result<Vec<Self::Real>> {
387                use eigh_generalized::*;
388                let work = EighGeneralizedWork::<$s>::new(calc_eigenvec, layout)?;
389                work.eval(uplo, a, b)
390            }
391
392            fn householder(l: MatrixLayout, a: &mut [Self]) -> Result<Vec<Self>> {
393                use qr::*;
394                let work = HouseholderWork::<$s>::new(l)?;
395                work.eval(a)
396            }
397
398            fn q(l: MatrixLayout, a: &mut [Self], tau: &[Self]) -> Result<()> {
399                use qr::*;
400                let mut work = QWork::<$s>::new(l)?;
401                work.calc(a, tau)?;
402                Ok(())
403            }
404
405            fn qr(l: MatrixLayout, a: &mut [Self]) -> Result<Vec<Self>> {
406                let tau = Self::householder(l, a)?;
407                let r = Vec::from(&*a);
408                Self::q(l, a, &tau)?;
409                Ok(r)
410            }
411
412            fn svd(
413                l: MatrixLayout,
414                calc_u: bool,
415                calc_vt: bool,
416                a: &mut [Self],
417            ) -> Result<SvdOwned<Self>> {
418                use svd::*;
419                let work = SvdWork::<$s>::new(l, calc_u, calc_vt)?;
420                work.eval(a)
421            }
422
423            fn svddc(layout: MatrixLayout, jobz: JobSvd, a: &mut [Self]) -> Result<SvdOwned<Self>> {
424                use svddc::*;
425                let work = SvdDcWork::<$s>::new(layout, jobz)?;
426                work.eval(a)
427            }
428
429            fn least_squares(
430                l: MatrixLayout,
431                a: &mut [Self],
432                b: &mut [Self],
433            ) -> Result<LeastSquaresOwned<Self>> {
434                let b_layout = l.resized(b.len() as i32, 1);
435                Self::least_squares_nrhs(l, a, b_layout, b)
436            }
437
438            fn least_squares_nrhs(
439                a_layout: MatrixLayout,
440                a: &mut [Self],
441                b_layout: MatrixLayout,
442                b: &mut [Self],
443            ) -> Result<LeastSquaresOwned<Self>> {
444                use least_squares::*;
445                let work = LeastSquaresWork::<$s>::new(a_layout, b_layout)?;
446                work.eval(a, b)
447            }
448
449            fn lu(l: MatrixLayout, a: &mut [Self]) -> Result<Pivot> {
450                use solve::*;
451                LuImpl::lu(l, a)
452            }
453
454            fn inv(l: MatrixLayout, a: &mut [Self], p: &Pivot) -> Result<()> {
455                use solve::*;
456                let mut work = InvWork::<$s>::new(l)?;
457                work.calc(a, p)?;
458                Ok(())
459            }
460
461            fn solve(
462                l: MatrixLayout,
463                t: Transpose,
464                a: &[Self],
465                p: &Pivot,
466                b: &mut [Self],
467            ) -> Result<()> {
468                use solve::*;
469                SolveImpl::solve(l, t, a, p, b)
470            }
471
472            fn bk(l: MatrixLayout, uplo: UPLO, a: &mut [Self]) -> Result<Pivot> {
473                use solveh::*;
474                let work = BkWork::<$s>::new(l)?;
475                work.eval(uplo, a)
476            }
477
478            fn invh(l: MatrixLayout, uplo: UPLO, a: &mut [Self], ipiv: &Pivot) -> Result<()> {
479                use solveh::*;
480                let mut work = InvhWork::<$s>::new(l)?;
481                work.calc(uplo, a, ipiv)
482            }
483
484            fn solveh(
485                l: MatrixLayout,
486                uplo: UPLO,
487                a: &[Self],
488                ipiv: &Pivot,
489                b: &mut [Self],
490            ) -> Result<()> {
491                use solveh::*;
492                SolvehImpl::solveh(l, uplo, a, ipiv, b)
493            }
494
495            fn cholesky(l: MatrixLayout, uplo: UPLO, a: &mut [Self]) -> Result<()> {
496                use cholesky::*;
497                CholeskyImpl::cholesky(l, uplo, a)
498            }
499
500            fn inv_cholesky(l: MatrixLayout, uplo: UPLO, a: &mut [Self]) -> Result<()> {
501                use cholesky::*;
502                InvCholeskyImpl::inv_cholesky(l, uplo, a)
503            }
504
505            fn solve_cholesky(
506                l: MatrixLayout,
507                uplo: UPLO,
508                a: &[Self],
509                b: &mut [Self],
510            ) -> Result<()> {
511                use cholesky::*;
512                SolveCholeskyImpl::solve_cholesky(l, uplo, a, b)
513            }
514
515            fn rcond(l: MatrixLayout, a: &[Self], anorm: Self::Real) -> Result<Self::Real> {
516                use rcond::*;
517                let mut work = RcondWork::<$s>::new(l);
518                work.calc(a, anorm)
519            }
520
521            fn opnorm(t: NormType, l: MatrixLayout, a: &[Self]) -> Self::Real {
522                use opnorm::*;
523                let mut work = OperatorNormWork::<$s>::new(t, l);
524                work.calc(a)
525            }
526
527            fn solve_triangular(
528                al: MatrixLayout,
529                bl: MatrixLayout,
530                uplo: UPLO,
531                d: Diag,
532                a: &[Self],
533                b: &mut [Self],
534            ) -> Result<()> {
535                use triangular::*;
536                SolveTriangularImpl::solve_triangular(al, bl, uplo, d, a, b)
537            }
538
539            fn lu_tridiagonal(a: Tridiagonal<Self>) -> Result<LUFactorizedTridiagonal<Self>> {
540                use tridiagonal::*;
541                let work = LuTridiagonalWork::<$s>::new(a.l);
542                work.eval(a)
543            }
544
545            fn rcond_tridiagonal(lu: &LUFactorizedTridiagonal<Self>) -> Result<Self::Real> {
546                use tridiagonal::*;
547                let mut work = RcondTridiagonalWork::<$s>::new(lu.a.l);
548                work.calc(lu)
549            }
550
551            fn solve_tridiagonal(
552                lu: &LUFactorizedTridiagonal<Self>,
553                bl: MatrixLayout,
554                t: Transpose,
555                b: &mut [Self],
556            ) -> Result<()> {
557                use tridiagonal::*;
558                SolveTridiagonalImpl::solve_tridiagonal(lu, bl, t, b)
559            }
560        }
561    };
562}
563impl_lapack!(c64);
564impl_lapack!(c32);
565impl_lapack!(f64);
566impl_lapack!(f32);