use super::{LapackError, ZERO_DETERMINANT};
use crate::Complex64;
fn zero() -> Complex64 {
Complex64::new(0.0, 0.0)
}
fn one() -> Complex64 {
Complex64::new(1.0, 0.0)
}
fn abs1(z: Complex64) -> f64 {
z.re.abs() + z.im.abs()
}
pub fn zgetrf(a: &mut [Complex64], n: usize) -> Result<Vec<usize>, LapackError> {
if a.len() != n * n {
return Err(LapackError::BadShape);
}
if n == 0 {
return Ok(Vec::new());
}
let mut ipiv = vec![0usize; n];
for j in 0..n {
let mut best = abs1(a[j + j * n]);
let mut jp = j;
for i in (j + 1)..n {
let cand = abs1(a[i + j * n]);
if cand > best {
best = cand;
jp = i;
}
}
ipiv[j] = jp;
if best <= ZERO_DETERMINANT {
return Err(LapackError::Singular);
}
if jp != j {
for k in 0..n {
a.swap(k * n + j, k * n + jp);
}
}
let d = a[j + j * n];
for i in (j + 1)..n {
a[i + j * n] /= d;
}
for k in (j + 1)..n {
let ajk = a[j + k * n];
for i in (j + 1)..n {
a[i + k * n] -= a[i + j * n] * ajk;
}
}
}
Ok(ipiv)
}
fn ztrti2_upper(a: &mut [Complex64], n: usize) -> Result<(), LapackError> {
for j in 0..n {
if a[j + j * n] == zero() {
return Err(LapackError::Singular);
}
let inv = one() / a[j + j * n];
a[j + j * n] = inv;
let ajj = -inv;
let mut x = vec![zero(); j];
for i in 0..j {
let mut sum = zero();
for k in i..j {
sum += a[i + k * n] * a[k + j * n];
}
x[i] = sum;
}
for i in 0..j {
a[i + j * n] = ajj * x[i];
}
}
Ok(())
}
pub fn zgetrf_rect(a: &mut [Complex64], 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 = abs1(a[k + k * nrows]);
for i in k + 1..nrows {
let cand = abs1(a[i + k * nrows]);
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 zgetri(lu: &mut [Complex64], n: usize, ipiv: &[usize]) -> Result<(), LapackError> {
if lu.len() != n * n || ipiv.len() != n {
return Err(LapackError::BadShape);
}
if n == 0 {
return Ok(());
}
ztrti2_upper(lu, n)?;
let mut work = vec![zero(); n];
let mut y = vec![zero(); n];
for j in (0..n).rev() {
for i in (j + 1)..n {
work[i] = lu[i + j * n];
lu[i + j * n] = zero();
}
if j + 1 < n {
for i in 0..n {
let mut sum = zero();
for k in (j + 1)..n {
sum += lu[i + k * n] * work[k];
}
y[i] = sum;
}
for i in 0..n {
lu[i + j * n] -= y[i];
}
}
}
for j in (0..n.saturating_sub(1)).rev() {
let jp = ipiv[j];
if jp != j {
for i in 0..n {
lu.swap(i + j * n, i + jp * n);
}
}
}
Ok(())
}
pub fn zgesv(a: &mut [Complex64], b: &mut [Complex64]) -> Result<(), LapackError> {
let n = b.len();
if a.len() != n * n {
return Err(LapackError::BadShape);
}
if n == 0 {
return Ok(());
}
let ipiv = zgetrf(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 zpotrf(a: &mut [Complex64], n: usize, upper: bool) -> Result<(), LapackError> {
if a.len() != n * n {
return Err(LapackError::BadShape);
}
if upper {
for j in 0..n {
let mut ajj = a[j + j * n].re;
for i in 0..j {
ajj -= a[i + j * n].norm_sqr();
}
if ajj <= 0.0 || ajj.is_nan() {
a[j + j * n] = Complex64::new(ajj, 0.0);
return Err(LapackError::Singular);
}
let ajj = ajj.sqrt();
a[j + j * n] = Complex64::new(ajj, 0.0);
if j + 1 < n {
for k in (j + 1)..n {
let mut sum = a[j + k * n];
for i in 0..j {
sum -= a[i + k * n] * a[i + j * n].conj();
}
a[j + k * n] = sum / ajj;
}
}
}
} else {
for j in 0..n {
let mut ajj = a[j + j * n].re;
for k in 0..j {
ajj -= a[j + k * n].norm_sqr();
}
if ajj <= 0.0 || ajj.is_nan() {
a[j + j * n] = Complex64::new(ajj, 0.0);
return Err(LapackError::Singular);
}
let ajj = ajj.sqrt();
a[j + j * n] = Complex64::new(ajj, 0.0);
for i in (j + 1)..n {
let mut sum = a[i + j * n];
for k in 0..j {
sum -= a[i + k * n] * a[j + k * n].conj();
}
a[i + j * n] = sum / ajj;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cpx;
fn spd3() -> Vec<Complex64> {
vec![
cpx!(5.0, 0.0),
cpx!(1.0, -1.0),
cpx!(0.0, 2.0), cpx!(1.0, 1.0),
cpx!(6.0, 0.0),
cpx!(1.0, 2.0), cpx!(0.0, -2.0),
cpx!(1.0, -2.0),
cpx!(7.0, 0.0), ]
}
fn general3() -> Vec<Complex64> {
vec![
cpx!(4.0, 1.0),
cpx!(1.0, 3.0),
cpx!(2.0, -1.0), cpx!(1.0, -2.0),
cpx!(3.0, 0.0),
cpx!(1.0, 1.0), cpx!(2.0, 0.0),
cpx!(0.0, -1.0),
cpx!(5.0, 2.0), ]
}
fn mat_vec(a: &[Complex64], x: &[Complex64], n: usize) -> Vec<Complex64> {
let mut y = vec![cpx!(0.0, 0.0); n];
for i in 0..n {
for j in 0..n {
y[i] += a[i + j * n] * x[j];
}
}
y
}
#[test]
fn zgetrf_then_zgetri_inverts() {
let a = general3();
let n = 3;
let mut lu = a.clone();
let piv = zgetrf(&mut lu, n).unwrap();
assert_eq!(piv.len(), n);
assert_eq!(piv[n - 1], n - 1, "the last pivot index is always n-1");
zgetri(&mut lu, n, &piv).unwrap();
for j in 0..n {
let mut e = vec![cpx!(0.0, 0.0); n];
e[j] = cpx!(1.0, 0.0);
let inv_col = mat_vec(&lu, &e, n); let back = mat_vec(&a, &inv_col, n);
for i in 0..n {
let want = if i == j { cpx!(1.0, 0.0) } else { cpx!(0.0, 0.0) };
assert!((back[i] - want).norm() < 1e-12, "A*A^-1 column {j} row {i} = {:?}", back[i]);
}
}
}
#[test]
fn zgesv_solves_a_system() {
let a = general3();
let n = 3;
let x_true = [cpx!(1.0, -2.0), cpx!(3.0, 0.5), cpx!(-1.0, 1.0)];
let mut b = mat_vec(&a, &x_true, n);
let mut a_work = a.clone();
zgesv(&mut a_work, &mut b).unwrap();
for i in 0..n {
assert!((b[i] - x_true[i]).norm() < 1e-12, "x[{i}] = {:?} vs {:?}", b[i], x_true[i]);
}
}
#[test]
fn zgetrf_detects_a_singular_matrix() {
let mut a = vec![
cpx!(1.0, 1.0),
cpx!(3.0, 0.0), cpx!(2.0, 2.0),
cpx!(6.0, 0.0), ];
assert_eq!(zgetrf(&mut a, 2), Err(LapackError::Singular));
}
#[test]
fn bad_shapes_are_rejected() {
let mut a = vec![cpx!(1.0, 0.0); 3];
assert_eq!(zgetrf(&mut a, 2), Err(LapackError::BadShape));
let ipiv = [0usize; 1];
assert_eq!(zgetri(&mut a, 2, &ipiv), Err(LapackError::BadShape));
let mut b = vec![cpx!(1.0, 0.0); 2];
assert_eq!(zgesv(&mut a, &mut b), Err(LapackError::BadShape));
assert_eq!(zpotrf(&mut a, 2, false), Err(LapackError::BadShape));
}
#[test]
fn zpotrf_factorises_both_triangles() {
let a = spd3();
let n = 3;
let mut lower = a.clone();
zpotrf(&mut lower, n, false).unwrap();
for i in 0..n {
for j in 0..=i {
let mut sum = cpx!(0.0, 0.0);
for k in 0..=j {
sum += lower[i + k * n] * lower[j + k * n].conj();
}
assert!(
(sum - a[i + j * n]).norm() < 1e-12,
"L L^H at ({i},{j}) = {:?} vs {:?}",
sum,
a[i + j * n]
);
}
}
let mut upper = a.clone();
zpotrf(&mut upper, n, true).unwrap();
for i in 0..n {
for j in i..n {
let mut sum = cpx!(0.0, 0.0);
for k in 0..=i {
sum += upper[k + i * n].conj() * upper[k + j * n];
}
assert!(
(sum - a[i + j * n]).norm() < 1e-12,
"U^H U at ({i},{j}) = {:?} vs {:?}",
sum,
a[i + j * n]
);
}
}
}
#[test]
fn zpotrf_rejects_a_non_positive_definite_matrix() {
let mut a = vec![cpx!(1.0, 0.0), cpx!(2.0, 0.0), cpx!(2.0, 0.0), cpx!(1.0, 0.0)];
assert_eq!(zpotrf(&mut a, 2, false), Err(LapackError::Singular));
}
#[test]
fn empty_matrices_are_handled() {
let mut a: Vec<Complex64> = Vec::new();
assert!(zgetrf(&mut a, 0).unwrap().is_empty());
assert_eq!(zgetri(&mut a, 0, &[]), Ok(()));
let mut b: Vec<Complex64> = Vec::new();
assert_eq!(zgesv(&mut a, &mut b), Ok(()));
assert_eq!(zpotrf(&mut a, 0, false), Ok(()));
}
}
#[cfg(test)]
mod rect_tests {
use super::*;
#[test]
fn zgetrf_rect_reconstructs_a_tall_panel() {
use crate::cpx;
let (nrows, ncols) = (3, 2);
let a0 = vec![
cpx!(1.0, 1.0),
cpx!(3.0, 0.0),
cpx!(2.0, -1.0),
cpx!(2.0, 0.0),
cpx!(0.0, -1.0),
cpx!(4.0, 0.0),
];
let mut a = a0.clone();
let mut ipiv = vec![0usize; ncols];
zgetrf_rect(&mut a, nrows, ncols, &mut ipiv).unwrap();
let mut pa = a0.clone();
for k in 0..ncols {
for j in 0..ncols {
pa.swap(j * nrows + k, j * nrows + ipiv[k]);
}
}
for j in 0..ncols {
for i in 0..nrows {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..ncols {
let l_il = if i == l {
Complex64::new(1.0, 0.0)
} else if i > l {
a[i + l * nrows]
} else {
Complex64::new(0.0, 0.0)
};
let u_lj = if l <= j { a[l + j * nrows] } else { Complex64::new(0.0, 0.0) };
sum += l_il * u_lj;
}
assert!((sum - pa[i + j * nrows]).norm() < 1e-12, "entry {i},{j}");
}
}
}
#[test]
fn zgetrf_rect_rejects_bad_shapes() {
let mut a = [crate::cpx!(1.0, 0.0); 4];
let mut ipiv = [0usize; 2];
assert_eq!(zgetrf_rect(&mut a, 1, 2, &mut ipiv).unwrap_err(), LapackError::BadShape);
}
}