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);