mod auxiliary;
pub mod complex;
pub mod eig;
pub mod qz;
pub mod svd;
pub mod sym;
pub mod zeig;
pub mod zsvd;
#[cfg(test)]
mod checkers;
#[cfg(test)]
mod examples;
#[cfg(test)]
pub(crate) mod families;
use super::blas;
pub const ZERO_DETERMINANT: f64 = 0.0;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LapackError {
Singular,
BadShape,
Convergence,
NotImplemented(&'static str),
}
pub fn dgetrf(a: &mut [f64], n: usize) -> Result<(Vec<usize>, f64), LapackError> {
if a.len() != n * n {
return Err(LapackError::BadShape);
}
if n == 0 {
return Ok((Vec::new(), 1.0));
}
let mut ipiv = vec![0usize; n];
let mut det = 1.0;
for k in 0..n {
let mut pivot = k;
let mut best = a[k + k * n].abs();
for i in (k + 1)..n {
let cand = a[i + k * n].abs();
if cand > best {
best = cand;
pivot = i;
}
}
if best <= ZERO_DETERMINANT {
return Err(LapackError::Singular);
}
if pivot != k {
for j in 0..n {
a.swap(j * n + k, j * n + pivot);
}
det = -det;
}
ipiv[k] = pivot;
let d = a[k + k * n];
det *= d;
for i in (k + 1)..n {
let factor = a[i + k * n] / d;
a[i + k * n] = factor;
for j in (k + 1)..n {
a[i + j * n] -= factor * a[k + j * n];
}
}
}
Ok((ipiv, det))
}
pub fn dgetrf_rect(a: &mut [f64], nrows: usize, ncols: usize, ipiv: &mut [usize]) -> Result<(), LapackError> {
if nrows < ncols || a.len() != nrows * ncols || ipiv.len() != ncols {
return Err(LapackError::BadShape);
}
if ncols == 0 {
return Ok(());
}
for k in 0..ncols {
let mut pivot = k;
let mut best = a[k + k * nrows].abs();
for i in k + 1..nrows {
let cand = a[i + k * nrows].abs();
if cand > best {
best = cand;
pivot = i;
}
}
if best <= ZERO_DETERMINANT {
return Err(LapackError::Singular);
}
if pivot != k {
for j in 0..ncols {
a.swap(j * nrows + k, j * nrows + pivot);
}
}
ipiv[k] = pivot;
let d = a[k + k * nrows];
for i in k + 1..nrows {
let factor = a[i + k * nrows] / d;
a[i + k * nrows] = factor;
for j in k + 1..ncols {
a[i + j * nrows] -= factor * a[k + j * nrows];
}
}
}
Ok(())
}
pub fn dgetri(lu: &mut [f64], n: usize, ipiv: &[usize]) -> Result<(), LapackError> {
if lu.len() != n * n || ipiv.len() != n {
return Err(LapackError::BadShape);
}
if n == 0 {
return Ok(());
}
let mut inverse = vec![0.0; n * n];
let mut b = vec![0.0; n];
for col in 0..n {
b.fill(0.0);
b[col] = 1.0;
for k in 0..n {
b.swap(k, ipiv[k]);
}
for i in 0..n {
let mut sum = b[i];
for j in 0..i {
sum -= lu[i + j * n] * b[j];
}
b[i] = sum;
}
for i in (0..n).rev() {
let mut sum = b[i];
for j in (i + 1)..n {
sum -= lu[i + j * n] * b[j];
}
let d = lu[i + i * n];
if d == 0.0 {
return Err(LapackError::Singular);
}
b[i] = sum / d;
}
for i in 0..n {
inverse[i + col * n] = b[i];
}
}
lu.copy_from_slice(&inverse);
Ok(())
}
pub fn dgesv(a: &mut [f64], b: &mut [f64]) -> Result<(), LapackError> {
let n = b.len();
if a.len() != n * n {
return Err(LapackError::BadShape);
}
if n == 0 {
return Ok(());
}
let (ipiv, _det) = dgetrf(a, n)?;
for k in 0..n {
b.swap(k, ipiv[k]);
}
for i in 0..n {
let mut sum = b[i];
for j in 0..i {
sum -= a[i + j * n] * b[j];
}
b[i] = sum;
}
for i in (0..n).rev() {
let mut sum = b[i];
for j in (i + 1)..n {
sum -= a[i + j * n] * b[j];
}
b[i] = sum / a[i + i * n];
}
Ok(())
}
pub fn dpotrf(a: &mut [f64], n: usize) -> Result<(), LapackError> {
if a.len() != n * n {
return Err(LapackError::BadShape);
}
for j in 0..n {
let mut sum = a[j + j * n];
for k in 0..j {
let ljk = a[j + k * n];
sum -= ljk * ljk;
}
if sum <= 0.0 {
return Err(LapackError::Singular);
}
let ljj = sum.sqrt();
a[j + j * n] = ljj;
for i in (j + 1)..n {
let mut s = a[i + j * n];
for k in 0..j {
s -= a[i + k * n] * a[j + k * n];
}
a[i + j * n] = s / ljj;
}
}
Ok(())
}
pub use super::blas::dlange;
#[cfg(test)]
pub(crate) fn dlange_by_code(norm_code: i32, a: &[f64], m: usize, n: usize) -> f64 {
super::blas::dlange(norm_char(norm_code), a, m, n)
}
pub fn norm_char(norm_code: i32) -> char {
use crate::constants::{NORM_EUC, NORM_FRO, NORM_INF, NORM_MAX};
if norm_code == NORM_EUC as i32 || norm_code == NORM_FRO as i32 {
'F'
} else if norm_code == NORM_INF as i32 {
'I'
} else if norm_code == NORM_MAX as i32 {
'M'
} else {
'O'
}
}
#[allow(unused_imports)]
use blas as _blas;
pub fn not_implemented(routine: &str) -> &'static str {
let _ = routine;
"this routine is not implemented in the native Rust LAPACK yet; build with `--features open_blas` (or `intel_mkl`) to use an external LAPACK"
}
#[cfg(test)]
mod tests {
use super::*;
fn sample4() -> Vec<f64> {
vec![
4.0, 1.0, 2.0, 1.0, 1.0, 3.0, 0.0, 1.0, 2.0, 0.0, 5.0, 2.0, 1.0, 1.0, 2.0, 6.0, ]
}
fn mat_vec(a: &[f64], x: &[f64], n: usize) -> Vec<f64> {
let mut y = vec![0.0; n];
blas::dgemv(1.0, a, x, 0.0, &mut y, n, n);
y
}
#[test]
fn dgetrf_determinant_and_pivots() {
let mut a = sample4();
let (ipiv, det) = dgetrf(&mut a, 4).unwrap();
assert_eq!(ipiv.len(), 4);
let expect = 4.0 * (3.0 * (5.0 * 6.0 - 2.0 * 2.0) - 0.0 * (0.0 * 6.0 - 2.0 * 1.0) + 1.0 * (0.0 * 2.0 - 5.0 * 1.0))
- 1.0 * (1.0 * (5.0 * 6.0 - 2.0 * 2.0) - 0.0 * (2.0 * 6.0 - 2.0 * 1.0) + 1.0 * (2.0 * 2.0 - 5.0 * 1.0))
+ 2.0 * (1.0 * (0.0 * 6.0 - 2.0 * 1.0) - 3.0 * (2.0 * 6.0 - 2.0 * 1.0) + 1.0 * (2.0 * 1.0 - 0.0 * 1.0))
- 1.0 * (1.0 * (0.0 * 2.0 - 5.0 * 1.0) - 3.0 * (2.0 * 2.0 - 5.0 * 1.0) + 0.0 * (2.0 * 1.0 - 0.0 * 1.0));
assert!((det - expect).abs() < 1e-12, "det {det} vs {expect}");
}
#[test]
fn dgetrf_detects_a_singular_matrix() {
let mut a = vec![1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 1.0, 1.0, 1.0];
assert_eq!(dgetrf(&mut a, 3), Err(LapackError::Singular));
}
#[test]
fn dgetrf_rejects_a_bad_shape() {
let mut a = vec![1.0, 2.0, 3.0];
assert_eq!(dgetrf(&mut a, 2), Err(LapackError::BadShape));
}
#[test]
fn dgetri_inverts_correctly() {
let a = sample4();
let mut lu = a.clone();
let (ipiv, _) = dgetrf(&mut lu, 4).unwrap();
dgetri(&mut lu, 4, &ipiv).unwrap();
for j in 0..4 {
let e = {
let mut v = vec![0.0; 4];
v[j] = 1.0;
v
};
let got = mat_vec(&a, &e, 4);
let expect = mat_vec(&lu, &e, 4); let _ = expect;
let mut back = vec![0.0; 4];
blas::dgemv(1.0, &lu, &got, 0.0, &mut back, 4, 4);
for i in 0..4 {
let want = if i == j { 1.0 } else { 0.0 };
assert!((back[i] - want).abs() < 1e-12, "A*A^-1 e_{j} component {i} = {}", back[i]);
}
}
}
#[test]
fn dgesv_solves_a_system() {
let a = sample4();
let x_true = vec![1.0, -2.0, 3.0, 5.0];
let mut b = mat_vec(&a, &x_true, 4);
let mut a_work = a.clone();
dgesv(&mut a_work, &mut b).unwrap();
for i in 0..4 {
assert!((b[i] - x_true[i]).abs() < 1e-12, "x[{}] = {} vs {}", i, b[i], x_true[i]);
}
}
#[test]
fn dgesv_reports_a_singular_system() {
let mut a = vec![1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 1.0, 1.0, 1.0];
let mut b = vec![1.0, 1.0, 1.0];
assert_eq!(dgesv(&mut a, &mut b), Err(LapackError::Singular));
}
#[test]
fn dpotrf_factorises_a_spd_matrix() {
let mut a = vec![4.0, 2.0, 2.0, 3.0];
dpotrf(&mut a, 2).unwrap();
assert!((a[0] - 2.0).abs() < 1e-14, "l00 = {}", a[0]);
assert!((a[1] - 1.0).abs() < 1e-14, "l10 = {}", a[1]);
assert!((a[3] - 2.0f64.sqrt()).abs() < 1e-14, "l11 = {}", a[3]);
let mut l = [0.0; 4];
for i in 0..2 {
for j in 0..2 {
if i >= j {
l[i + j * 2] = a[i + j * 2];
}
}
}
let mut recon = [0.0; 4];
for i in 0..2 {
for j in 0..2 {
let mut sum = 0.0;
for k in 0..2 {
sum += l[i + k * 2] * l[j + k * 2];
}
recon[i + j * 2] = sum;
}
}
assert!((recon[0] - 4.0).abs() < 1e-12);
assert!((recon[1] - 2.0).abs() < 1e-12);
assert!((recon[3] - 3.0).abs() < 1e-12);
}
#[test]
fn dpotrf_rejects_a_non_positive_definite_matrix() {
let mut a = vec![1.0, 2.0, 2.0, 1.0];
assert_eq!(dpotrf(&mut a, 2), Err(LapackError::Singular));
let mut b = vec![1.0, 1.0, 1.0, 1.0];
assert_eq!(dpotrf(&mut b, 2), Err(LapackError::Singular));
}
#[test]
fn empty_matrices_are_handled() {
let mut a: Vec<f64> = Vec::new();
assert_eq!(dgetrf(&mut a, 0).unwrap(), (Vec::new(), 1.0));
let mut b: Vec<f64> = Vec::new();
assert_eq!(dgesv(&mut a, &mut b), Ok(()));
assert_eq!(dpotrf(&mut a, 0), Ok(()));
}
#[test]
fn norm_char_matches_the_shim_mapping() {
use crate::constants::{NORM_EUC, NORM_FRO, NORM_INF, NORM_MAX, NORM_ONE};
assert_eq!(norm_char(NORM_EUC as i32), 'F');
assert_eq!(norm_char(NORM_FRO as i32), 'F');
assert_eq!(norm_char(NORM_INF as i32), 'I');
assert_eq!(norm_char(NORM_MAX as i32), 'M');
assert_eq!(norm_char(NORM_ONE as i32), 'O');
assert_eq!(norm_char(99), 'O');
let a = [1.0, 3.0, 2.0, 4.0];
let fro = (30.0f64).sqrt();
let norms = [
(NORM_EUC as i32, fro),
(NORM_FRO as i32, fro),
(NORM_INF as i32, 7.0),
(NORM_MAX as i32, 4.0),
(NORM_ONE as i32, 6.0),
];
for (code, want) in norms {
let got = dlange_by_code(code, &a, 2, 2);
assert!((got - want).abs() <= 1e-14, "code {code} ('{}'): {got} != {want}", norm_char(code));
}
assert_eq!(dlange_by_code(NORM_ONE as i32, &a, 2, 2), super::super::blas::dlange('O', &a, 2, 2));
}
}
#[cfg(test)]
mod zero_determinant_tests {
use super::*;
#[test]
fn pivot_threshold_matches_lapack() {
let mut a = vec![1e-16, 0.0, 0.0, 1.0];
let (ipiv, det) = dgetrf(&mut a, 2).expect("a non-zero pivot is not singular");
assert_eq!(ipiv.len(), 2);
assert!(det.abs() < 1e-15, "det = {det}");
let mut b = vec![1.0, 2.0, 2.0, 4.0];
assert_eq!(dgetrf(&mut b, 2), Err(LapackError::Singular));
}
}
#[cfg(test)]
mod panel_tests {
use super::*;
fn reconstruct(nrows: usize, ncols: usize, original: &[f64], a: &[f64], ipiv: &[usize]) -> Vec<f64> {
let mut pa = original.to_vec();
for k in 0..ncols {
for j in 0..ncols {
pa.swap(j * nrows + k, j * nrows + ipiv[k]);
}
}
let mut lu = vec![0.0; nrows * ncols];
for j in 0..ncols {
for i in 0..nrows {
let mut sum = 0.0;
for l in 0..ncols {
let l_il = if i == l {
1.0
} else if i > l {
a[i + l * nrows]
} else {
0.0
};
let u_lj = if l <= j { a[l + j * nrows] } else { 0.0 };
sum += l_il * u_lj;
}
lu[i + j * nrows] = sum;
}
}
for (idx, value) in pa.iter_mut().enumerate() {
assert!((*value - lu[idx]).abs() < 1e-12, "entry {idx}");
}
lu
}
#[test]
fn dgetrf_rect_reconstructs_a_tall_panel() {
let (nrows, ncols) = (5, 3);
let a0 = vec![1.0, 5.0, 2.0, 3.0, 4.0, 2.0, -1.0, 6.0, 0.0, 1.0, 3.0, 2.0, 1.0, 7.0, -2.0];
let mut a = a0.clone();
let mut ipiv = vec![0usize; ncols];
dgetrf_rect(&mut a, nrows, ncols, &mut ipiv).unwrap();
reconstruct(nrows, ncols, &a0, &a, &ipiv);
assert_eq!(ipiv[0], 1);
}
#[test]
fn dgetrf_rect_rejects_bad_shapes_and_singular_panels() {
let mut a = [1.0, 2.0, 3.0, 4.0];
let mut ipiv = [0usize; 2];
assert_eq!(dgetrf_rect(&mut a, 1, 2, &mut ipiv).unwrap_err(), LapackError::BadShape);
assert_eq!(dgetrf_rect(&mut a, 2, 2, &mut ipiv[..1]).unwrap_err(), LapackError::BadShape);
let mut singular = [0.0, 0.0, 2.0, 3.0];
assert_eq!(dgetrf_rect(&mut singular, 2, 2, &mut ipiv).unwrap_err(), LapackError::Singular);
}
#[test]
fn dgetrf_rect_accepts_zero_columns() {
let mut ipiv = [0usize; 0];
dgetrf_rect(&mut [], 3, 0, &mut ipiv).unwrap();
}
}