use super::LapackError;
use super::auxiliary::{dlapy3, dlascl_g, zladiv, zlarf_left, zlarf_right, zlascl_g};
use super::blas::dznrm2;
use super::eig::dlartg;
use super::svd::{dlas2, dlasv2};
use crate::Complex64;
const EPS: f64 = f64::EPSILON * 0.5;
const PREC: f64 = f64::EPSILON;
const SAFMIN: f64 = f64::MIN_POSITIVE;
#[inline]
fn zero() -> Complex64 {
Complex64::new(0.0, 0.0)
}
#[inline]
fn one() -> Complex64 {
Complex64::new(1.0, 0.0)
}
#[inline]
fn scale(c: Complex64, r: f64) -> Complex64 {
Complex64::new(c.re * r, c.im * r)
}
#[inline]
fn fsign(a: f64, b: f64) -> f64 {
if b >= 0.0 { a.abs() } else { -a.abs() }
}
fn zlarfg(alpha: &mut Complex64, x: &mut [Complex64]) -> Complex64 {
let mut xnrm = dznrm2(x);
let mut alphr = alpha.re;
let mut alphi = alpha.im;
if xnrm == 0.0 && alphi == 0.0 {
return zero();
}
let safmin = f64::MIN_POSITIVE / (f64::EPSILON / 2.0);
let rsafmn = 1.0 / safmin;
let mut beta = -sign_f(dlapy3(alphr, alphi, xnrm), alphr);
let mut knt = 0usize;
if beta.abs() < safmin {
loop {
knt += 1;
for xi in x.iter_mut() {
*xi = scale(*xi, rsafmn);
}
beta *= rsafmn;
alphi *= rsafmn;
alphr *= rsafmn;
if !(beta.abs() < safmin && knt < 20) {
break;
}
}
xnrm = dznrm2(x);
beta = -sign_f(dlapy3(alphr, alphi, xnrm), alphr);
}
let tau = Complex64::new((beta - alphr) / beta, -alphi / beta);
let al = zladiv(one(), Complex64::new(alphr - beta, alphi));
for xi in x.iter_mut() {
*xi *= al;
}
for _ in 0..knt {
beta *= safmin;
}
*alpha = Complex64::new(beta, 0.0);
tau
}
fn sign_f(a: f64, b: f64) -> f64 {
if b < 0.0 { -a.abs() } else { a.abs() }
}
fn zlasr_left(nrows: usize, ncols: usize, c: &[f64], s: &[f64], a: &mut [Complex64], lda: usize, r0: usize, c0: usize, backward: bool) {
if nrows < 2 || ncols == 0 {
return;
}
let count = nrows - 1;
for jj in 0..count {
let j = if backward { count - 1 - jj } else { jj };
let ctemp = c[j];
let stemp = s[j];
if ctemp != 1.0 || stemp != 0.0 {
for i in 0..ncols {
let temp = a[(r0 + j + 1) + (c0 + i) * lda];
a[(r0 + j + 1) + (c0 + i) * lda] = scale(temp, ctemp) - scale(a[(r0 + j) + (c0 + i) * lda], stemp);
a[(r0 + j) + (c0 + i) * lda] = scale(temp, stemp) + scale(a[(r0 + j) + (c0 + i) * lda], ctemp);
}
}
}
}
fn zlasr_right(nrows: usize, ncols: usize, c: &[f64], s: &[f64], a: &mut [Complex64], lda: usize, r0: usize, c0: usize, backward: bool) {
if ncols < 2 || nrows == 0 {
return;
}
let count = ncols - 1;
for jj in 0..count {
let j = if backward { count - 1 - jj } else { jj };
let ctemp = c[j];
let stemp = s[j];
if ctemp != 1.0 || stemp != 0.0 {
for i in 0..nrows {
let temp = a[(r0 + i) + (c0 + j + 1) * lda];
a[(r0 + i) + (c0 + j + 1) * lda] = scale(temp, ctemp) - scale(a[(r0 + i) + (c0 + j) * lda], stemp);
a[(r0 + i) + (c0 + j) * lda] = scale(temp, stemp) + scale(a[(r0 + i) + (c0 + j) * lda], ctemp);
}
}
}
}
pub fn zgebd2(a: &mut [Complex64], lda: usize, m: usize, n: usize, d: &mut [f64], e: &mut [f64], tauq: &mut [Complex64], taup: &mut [Complex64]) {
if m >= n {
for i in 1..=n {
let mut alpha = a[(i - 1) + (i - 1) * lda];
let mut x: Vec<Complex64> = ((i + 1)..=m).map(|r| a[(r - 1) + (i - 1) * lda]).collect();
let tau = zlarfg(&mut alpha, &mut x);
a[(i - 1) + (i - 1) * lda] = alpha;
tauq[i - 1] = tau;
for (k, r) in ((i + 1)..=m).enumerate() {
a[(r - 1) + (i - 1) * lda] = x[k];
}
d[i - 1] = alpha.re;
if i < n {
let mrows = m - i + 1;
let mut v = vec![zero(); mrows];
v[0] = one();
for k in 1..mrows {
v[k] = a[(i + k - 1) + (i - 1) * lda];
}
zlarf_left(a, lda, i - 1, mrows, i, n - i, &v, tau.conj());
a[(i - 1) + (i - 1) * lda] = Complex64::new(d[i - 1], 0.0);
for c in (i + 1)..=n {
a[(i - 1) + (c - 1) * lda] = a[(i - 1) + (c - 1) * lda].conj();
}
let mut alpha = a[(i - 1) + i * lda];
let mut x: Vec<Complex64> = ((i + 2)..=n).map(|c| a[(i - 1) + (c - 1) * lda]).collect();
let tau = zlarfg(&mut alpha, &mut x);
a[(i - 1) + i * lda] = alpha;
taup[i - 1] = tau;
for (k, c) in ((i + 2)..=n).enumerate() {
a[(i - 1) + (c - 1) * lda] = x[k];
}
e[i - 1] = alpha.re;
let ncols = n - i;
let mut v = vec![zero(); ncols];
v[0] = one();
for k in 1..ncols {
v[k] = a[(i - 1) + (i + k) * lda];
}
zlarf_right(a, lda, i, m - i, i, ncols, &v, tau);
for c in (i + 1)..=n {
a[(i - 1) + (c - 1) * lda] = a[(i - 1) + (c - 1) * lda].conj();
}
a[(i - 1) + i * lda] = Complex64::new(e[i - 1], 0.0);
} else {
taup[i - 1] = zero();
}
}
} else {
for i in 1..=m {
for c in i..=n {
a[(i - 1) + (c - 1) * lda] = a[(i - 1) + (c - 1) * lda].conj();
}
let mut alpha = a[(i - 1) + (i - 1) * lda];
let mut x: Vec<Complex64> = ((i + 1)..=n).map(|c| a[(i - 1) + (c - 1) * lda]).collect();
let tau = zlarfg(&mut alpha, &mut x);
a[(i - 1) + (i - 1) * lda] = alpha;
taup[i - 1] = tau;
for (k, c) in ((i + 1)..=n).enumerate() {
a[(i - 1) + (c - 1) * lda] = x[k];
}
d[i - 1] = alpha.re;
if i < m {
let ncols = n - i + 1;
let mut v = vec![zero(); ncols];
v[0] = one();
for k in 1..ncols {
v[k] = a[(i - 1) + (i + k - 1) * lda];
}
zlarf_right(a, lda, i, m - i, i - 1, ncols, &v, tau);
}
for c in i..=n {
a[(i - 1) + (c - 1) * lda] = a[(i - 1) + (c - 1) * lda].conj();
}
a[(i - 1) + (i - 1) * lda] = Complex64::new(d[i - 1], 0.0);
if i < m {
let mut alpha = a[i + (i - 1) * lda];
let mut x: Vec<Complex64> = ((i + 2)..=m).map(|r| a[(r - 1) + (i - 1) * lda]).collect();
let tau = zlarfg(&mut alpha, &mut x);
a[i + (i - 1) * lda] = alpha;
tauq[i - 1] = tau;
for (k, r) in ((i + 2)..=m).enumerate() {
a[(r - 1) + (i - 1) * lda] = x[k];
}
e[i - 1] = alpha.re;
let mrows = m - i;
let mut v = vec![zero(); mrows];
v[0] = one();
for k in 1..mrows {
v[k] = a[(i + k) + (i - 1) * lda];
}
zlarf_left(a, lda, i, mrows, i, n - i, &v, tau.conj());
a[i + (i - 1) * lda] = Complex64::new(e[i - 1], 0.0);
} else {
tauq[i - 1] = zero();
}
}
}
}
fn zung2r(m: usize, n: usize, k: usize, a: &mut [Complex64], lda: usize, tau: &[Complex64]) {
if n == 0 {
return;
}
for j in k..n {
for l in 0..m {
a[l + j * lda] = zero();
}
a[j + j * lda] = one();
}
for i in (0..k).rev() {
if i < n - 1 {
let mrows = m - i;
let ncols = n - i - 1;
let mut v = vec![zero(); mrows];
v[0] = one();
for t in 1..mrows {
v[t] = a[(i + t) + i * lda];
}
zlarf_left(a, lda, i, mrows, i + 1, ncols, &v, tau[i]);
}
if i < m - 1 {
for t in (i + 1)..m {
a[t + i * lda] *= -tau[i];
}
}
a[i + i * lda] = one() - tau[i];
for l in 0..i {
a[l + i * lda] = zero();
}
}
}
fn zungl2(m: usize, n: usize, k: usize, a: &mut [Complex64], lda: usize, tau: &[Complex64]) {
if m == 0 {
return;
}
if k < m {
for j in 0..n {
for l in k..m {
a[l + j * lda] = zero();
}
if j >= k && j < m {
a[j + j * lda] = one();
}
}
}
for i in (0..k).rev() {
if i < n - 1 {
for c in (i + 1)..n {
a[i + c * lda] = a[i + c * lda].conj();
}
if i < m - 1 {
let nrows = m - i;
let ncols = n - i;
let mut v = vec![zero(); ncols];
v[0] = one();
for t in 1..ncols {
v[t] = a[i + (i + t) * lda];
}
zlarf_right(a, lda, i + 1, nrows - 1, i, ncols, &v, tau[i].conj());
}
for c in (i + 1)..n {
a[i + c * lda] *= -tau[i];
}
for c in (i + 1)..n {
a[i + c * lda] = a[i + c * lda].conj();
}
}
a[i + i * lda] = one() - tau[i].conj();
for l in 0..i {
a[i + l * lda] = zero();
}
}
}
pub fn zungbr(want_q: bool, m: usize, n: usize, k: usize, a: &mut [Complex64], lda: usize, tau: &[Complex64]) {
if m == 0 || n == 0 {
return;
}
if want_q {
if m >= k {
zung2r(m, n, k, a, lda, tau);
} else {
for j in (2..=m).rev() {
a[(j - 1) * lda] = zero();
for i in (j + 1)..=m {
a[(i - 1) + (j - 1) * lda] = a[(i - 1) + (j - 2) * lda];
}
}
a[0] = one();
for i in 2..=m {
a[i - 1] = zero();
}
if m > 1 {
zung2r(m - 1, m - 1, m - 1, &mut a[1 + lda..], lda, tau);
}
}
} else if k < n {
zungl2(m, n, k, a, lda, tau);
} else {
a[0] = one();
for i in 2..=n {
a[i - 1] = zero();
}
for j in 2..=n {
for i in (2..=j - 1).rev() {
a[(i - 1) + (j - 1) * lda] = a[(i - 2) + (j - 1) * lda];
}
a[(j - 1) * lda] = zero();
}
if n > 1 {
zungl2(n - 1, n - 1, n - 1, &mut a[1 + lda..], lda, tau);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn zbdsqr(
lower: bool,
n: usize,
ncvt: usize,
nru: usize,
ncc: usize,
d: &mut [f64],
e: &mut [f64],
vt: &mut [Complex64],
ldvt: usize,
u: &mut [Complex64],
ldu: usize,
c: &mut [Complex64],
ldc: usize,
) -> usize {
const ZERO: f64 = 0.0;
const ONE: f64 = 1.0;
const HNDRTH: f64 = 0.01;
const TEN: f64 = 10.0;
const HNDRD: f64 = 100.0;
const MEIGTH: f64 = -0.125;
const MAXITR: usize = 6;
if n == 0 {
return 0;
}
let mut work = vec![0.0; 4 * n];
let nm1 = n - 1;
let nm12 = nm1 + nm1;
let nm13 = nm12 + nm1;
let mut idir: usize = 0;
let unfl = SAFMIN;
if lower {
for i in 1..=nm1 {
let (cs, sn, r) = dlartg(d[i - 1], e[i - 1]);
d[i - 1] = r;
e[i - 1] = sn * d[i];
d[i] *= cs;
work[i - 1] = cs;
work[nm1 + i - 1] = sn;
}
if nru > 0 {
zlasr_right(nru, n, &work, &work[nm1..], u, ldu, 0, 0, false);
}
if ncc > 0 {
zlasr_left(n, ncc, &work, &work[nm1..], c, ldc, 0, 0, false);
}
}
let tolmul = TEN.max(HNDRD.min(EPS.powf(MEIGTH)));
let tol = tolmul * EPS;
let mut smax = ZERO;
for i in 1..=n {
smax = smax.max(d[i - 1].abs());
}
for i in 1..=nm1 {
smax = smax.max(e[i - 1].abs());
}
let mut smin = ZERO;
let thresh;
if tol >= ZERO {
let mut sminoa = d[0].abs();
if sminoa != ZERO {
let mut mu = sminoa;
for i in 2..=n {
mu = d[i - 1].abs() * (mu / (mu + e[i - 2].abs()));
sminoa = sminoa.min(mu);
if sminoa == ZERO {
break;
}
}
}
sminoa /= (n as f64).sqrt();
thresh = (tol * sminoa).max(MAXITR as f64 * (n as f64 * (n as f64 * unfl)));
} else {
thresh = (tol.abs() * smax).max(MAXITR as f64 * (n as f64 * (n as f64 * unfl)));
}
let maxitdivn = MAXITR * n;
let mut iterdivn = 0usize;
let mut iter: isize = -1;
let mut oldll: isize = -1;
let mut oldm: isize = -1;
let mut m = n;
let info = 0usize;
loop {
if m <= 1 {
break;
}
if iter >= n as isize {
iter -= n as isize;
iterdivn += 1;
if iterdivn >= maxitdivn {
let mut count = 0usize;
for i in 1..=nm1 {
if e[i - 1] != ZERO {
count += 1;
}
}
return count;
}
}
if tol < ZERO && d[m - 1].abs() <= thresh {
d[m - 1] = ZERO;
}
smax = d[m - 1].abs();
let mut ll = 0usize;
let mut split = false;
for lll in 1..=m - 1 {
ll = m - lll;
let abss = d[ll - 1].abs();
let abse = e[ll - 1].abs();
if tol < ZERO && abss <= thresh {
d[ll - 1] = ZERO;
}
if abse <= thresh {
split = true;
break;
}
smax = smax.max(abss).max(abse);
}
if !split {
ll = 0;
} else {
e[ll - 1] = ZERO;
if ll == m - 1 {
m -= 1;
continue;
}
}
ll += 1;
if ll == m - 1 {
let (sigmn, sigmax, sinr, cosr, sinl, cosl) = dlasv2(d[m - 2], e[m - 2], d[m - 1]);
d[m - 2] = sigmax;
e[m - 2] = ZERO;
d[m - 1] = sigmn;
if ncvt > 0 {
for k in 0..ncvt {
let x = vt[(m - 2) + k * ldvt];
let y = vt[(m - 1) + k * ldvt];
vt[(m - 2) + k * ldvt] = scale(x, cosr) + scale(y, sinr);
vt[(m - 1) + k * ldvt] = scale(y, cosr) - scale(x, sinr);
}
}
if nru > 0 {
for k in 0..nru {
let x = u[k + (m - 2) * ldu];
let y = u[k + (m - 1) * ldu];
u[k + (m - 2) * ldu] = scale(x, cosl) + scale(y, sinl);
u[k + (m - 1) * ldu] = scale(y, cosl) - scale(x, sinl);
}
}
if ncc > 0 {
for k in 0..ncc {
let x = c[(m - 2) + k * ldc];
let y = c[(m - 1) + k * ldc];
c[(m - 2) + k * ldc] = scale(x, cosl) + scale(y, sinl);
c[(m - 1) + k * ldc] = scale(y, cosl) - scale(x, sinl);
}
}
m -= 2;
continue;
}
if ll as isize > oldm || (m as isize) < oldll {
idir = if d[ll - 1].abs() >= d[m - 1].abs() { 1 } else { 2 };
}
if idir == 1 {
if e[m - 2].abs() <= tol.abs() * d[m - 1].abs() || (tol < ZERO && e[m - 2].abs() <= thresh) {
e[m - 2] = ZERO;
continue;
}
if tol >= ZERO {
let mut mu = d[ll - 1].abs();
smin = mu;
let mut deflated = false;
for lll in ll..=m - 1 {
if e[lll - 1].abs() <= tol * mu {
e[lll - 1] = ZERO;
deflated = true;
break;
}
mu = d[lll].abs() * (mu / (mu + e[lll - 1].abs()));
smin = smin.min(mu);
}
if deflated {
continue;
}
}
} else {
if e[ll - 1].abs() <= tol.abs() * d[ll - 1].abs() || (tol < ZERO && e[ll - 1].abs() <= thresh) {
e[ll - 1] = ZERO;
continue;
}
if tol >= ZERO {
let mut mu = d[m - 1].abs();
smin = mu;
let mut deflated = false;
for lll in (ll..=m - 1).rev() {
if e[lll - 1].abs() <= tol * mu {
e[lll - 1] = ZERO;
deflated = true;
break;
}
mu = d[lll - 1].abs() * (mu / (mu + e[lll - 1].abs()));
smin = smin.min(mu);
}
if deflated {
continue;
}
}
}
oldll = ll as isize;
oldm = m as isize;
let shift;
if tol >= ZERO && (n as f64) * tol * (smin / smax) <= PREC.max(HNDRTH * tol) {
shift = ZERO;
} else {
let (sh, sll);
if idir == 1 {
sll = d[ll - 1].abs();
sh = dlas2(d[m - 2], e[m - 2], d[m - 1]).0;
} else {
sll = d[m - 1].abs();
sh = dlas2(d[ll - 1], e[ll - 1], d[ll]).0;
}
shift = if sll > ZERO && (sh / sll).powi(2) < EPS { ZERO } else { sh };
}
iter += m as isize - ll as isize;
if shift == ZERO {
if idir == 1 {
let mut cs = ONE;
let mut oldcs = ONE;
let mut sn;
let mut oldsn = ZERO;
for i in ll..=m - 1 {
let (c, s, r) = dlartg(d[i - 1] * cs, e[i - 1]);
cs = c;
sn = s;
if i > ll {
e[i - 2] = oldsn * r;
}
let (c, s, r) = dlartg(oldcs * r, d[i] * sn);
oldcs = c;
oldsn = s;
d[i - 1] = r;
work[i - ll] = cs;
work[i - ll + nm1] = sn;
work[i - ll + nm12] = oldcs;
work[i - ll + nm13] = oldsn;
}
let h = d[m - 1] * cs;
d[m - 1] = h * oldcs;
e[m - 2] = h * oldsn;
if ncvt > 0 {
zlasr_left(m - ll + 1, ncvt, &work, &work[nm1..], vt, ldvt, ll - 1, 0, false);
}
if nru > 0 {
zlasr_right(nru, m - ll + 1, &work[nm12..], &work[nm13..], u, ldu, 0, ll - 1, false);
}
if ncc > 0 {
zlasr_left(m - ll + 1, ncc, &work[nm12..], &work[nm13..], c, ldc, ll - 1, 0, false);
}
if e[m - 2].abs() <= thresh {
e[m - 2] = ZERO;
}
} else {
let mut cs = ONE;
let mut oldcs = ONE;
let mut sn;
let mut oldsn = ZERO;
for i in (ll + 1..=m).rev() {
let (c, s, r) = dlartg(d[i - 1] * cs, e[i - 2]);
cs = c;
sn = s;
if i < m {
e[i - 1] = oldsn * r;
}
let (c, s, r) = dlartg(oldcs * r, d[i - 2] * sn);
oldcs = c;
oldsn = s;
d[i - 1] = r;
work[i - ll - 1] = cs;
work[i - ll - 1 + nm1] = -sn;
work[i - ll - 1 + nm12] = oldcs;
work[i - ll - 1 + nm13] = -oldsn;
}
let h = d[ll - 1] * cs;
d[ll - 1] = h * oldcs;
e[ll - 1] = h * oldsn;
if ncvt > 0 {
zlasr_left(m - ll + 1, ncvt, &work[nm12..], &work[nm13..], vt, ldvt, ll - 1, 0, true);
}
if nru > 0 {
zlasr_right(nru, m - ll + 1, &work, &work[nm1..], u, ldu, 0, ll - 1, true);
}
if ncc > 0 {
zlasr_left(m - ll + 1, ncc, &work, &work[nm1..], c, ldc, ll - 1, 0, true);
}
if e[ll - 1].abs() <= thresh {
e[ll - 1] = ZERO;
}
}
} else if idir == 1 {
let mut f = (d[ll - 1].abs() - shift) * (fsign(ONE, d[ll - 1]) + shift / d[ll - 1]);
let mut g = e[ll - 1];
for i in ll..=m - 1 {
let (cosr, sinr, r) = dlartg(f, g);
if i > ll {
e[i - 2] = r;
}
f = cosr * d[i - 1] + sinr * e[i - 1];
e[i - 1] = cosr * e[i - 1] - sinr * d[i - 1];
g = sinr * d[i];
d[i] *= cosr;
let (cosl, sinl, r) = dlartg(f, g);
d[i - 1] = r;
f = cosl * e[i - 1] + sinl * d[i];
d[i] = cosl * d[i] - sinl * e[i - 1];
if i < m - 1 {
g = sinl * e[i];
e[i] *= cosl;
}
work[i - ll] = cosr;
work[i - ll + nm1] = sinr;
work[i - ll + nm12] = cosl;
work[i - ll + nm13] = sinl;
}
e[m - 2] = f;
if ncvt > 0 {
zlasr_left(m - ll + 1, ncvt, &work, &work[nm1..], vt, ldvt, ll - 1, 0, false);
}
if nru > 0 {
zlasr_right(nru, m - ll + 1, &work[nm12..], &work[nm13..], u, ldu, 0, ll - 1, false);
}
if ncc > 0 {
zlasr_left(m - ll + 1, ncc, &work[nm12..], &work[nm13..], c, ldc, ll - 1, 0, false);
}
if e[m - 2].abs() <= thresh {
e[m - 2] = ZERO;
}
} else {
let mut f = (d[m - 1].abs() - shift) * (fsign(ONE, d[m - 1]) + shift / d[m - 1]);
let mut g = e[m - 2];
for i in (ll + 1..=m).rev() {
let (cosr, sinr, r) = dlartg(f, g);
if i < m {
e[i - 1] = r;
}
f = cosr * d[i - 1] + sinr * e[i - 2];
e[i - 2] = cosr * e[i - 2] - sinr * d[i - 1];
g = sinr * d[i - 2];
d[i - 2] *= cosr;
let (cosl, sinl, r) = dlartg(f, g);
d[i - 1] = r;
f = cosl * e[i - 2] + sinl * d[i - 2];
d[i - 2] = cosl * d[i - 2] - sinl * e[i - 2];
if i > ll + 1 {
g = sinl * e[i - 3];
e[i - 3] *= cosl;
}
work[i - ll - 1] = cosr;
work[i - ll - 1 + nm1] = -sinr;
work[i - ll - 1 + nm12] = cosl;
work[i - ll - 1 + nm13] = -sinl;
}
e[ll - 1] = f;
if e[ll - 1].abs() <= thresh {
e[ll - 1] = ZERO;
}
if ncvt > 0 {
zlasr_left(m - ll + 1, ncvt, &work[nm12..], &work[nm13..], vt, ldvt, ll - 1, 0, true);
}
if nru > 0 {
zlasr_right(nru, m - ll + 1, &work, &work[nm1..], u, ldu, 0, ll - 1, true);
}
if ncc > 0 {
zlasr_left(m - ll + 1, ncc, &work, &work[nm1..], c, ldc, ll - 1, 0, true);
}
}
}
for i in 1..=n {
if d[i - 1] == ZERO {
d[i - 1] = ZERO;
}
if d[i - 1] < ZERO {
d[i - 1] = -d[i - 1];
if ncvt > 0 {
for k in 0..ncvt {
vt[(i - 1) + k * ldvt] *= Complex64::new(-1.0, 0.0);
}
}
}
}
for i in 1..=nm1 {
let mut isub = 1usize;
let mut smallest = d[0];
for j in 2..=(n + 1 - i) {
if d[j - 1] <= smallest {
isub = j;
smallest = d[j - 1];
}
}
if isub != n + 1 - i {
d[isub - 1] = d[n - i];
d[n - i] = smallest;
if ncvt > 0 {
for k in 0..ncvt {
vt.swap((isub - 1) + k * ldvt, (n - i) + k * ldvt);
}
}
if nru > 0 {
for k in 0..nru {
u.swap(k + (isub - 1) * ldu, k + (n - i) * ldu);
}
}
if ncc > 0 {
for k in 0..ncc {
c.swap((isub - 1) + k * ldc, (n - i) + k * ldc);
}
}
}
}
info
}
pub fn zgesvd(a: &mut [Complex64], m: usize, n: usize, s: &mut [f64], u: &mut [Complex64], vt: &mut [Complex64]) -> Result<(), LapackError> {
if a.len() != m * n {
return Err(LapackError::BadShape);
}
if m == 0 || n == 0 {
return Ok(());
}
let minmn = m.min(n);
if s.len() != minmn || u.len() != m * m || vt.len() != n * n {
return Err(LapackError::BadShape);
}
let eps = PREC;
let smlnum = SAFMIN.sqrt() / eps;
let bignum = 1.0 / smlnum;
let anrm = super::super::blas::zlange('M', a, m, n);
let mut iscl = false;
if anrm > 0.0 && anrm < smlnum {
iscl = true;
zlascl_g(anrm, smlnum, a, m, m, n);
} else if anrm > bignum {
iscl = true;
zlascl_g(anrm, bignum, a, m, m, n);
}
let mut e = vec![0.0; minmn];
let mut tauq = vec![zero(); minmn];
let mut taup = vec![zero(); minmn];
let info;
if m >= n {
zgebd2(a, m, m, n, s, &mut e, &mut tauq, &mut taup);
for j in 0..n {
for i in j..m {
u[i + j * m] = a[i + j * m];
}
}
zungbr(true, m, m, n, u, m, &tauq);
for j in 0..n {
for i in 0..=j {
vt[i + j * n] = a[i + j * m];
}
}
zungbr(false, n, n, n, vt, n, &taup);
info = zbdsqr(false, n, n, m, 0, s, &mut e, vt, n, u, m, &mut [], 0);
} else {
zgebd2(a, m, m, n, s, &mut e, &mut tauq, &mut taup);
for j in 0..m {
for i in j..m {
u[i + j * m] = a[i + j * m];
}
}
zungbr(true, m, m, n, u, m, &tauq);
for j in 0..n {
for i in 0..=j.min(m - 1) {
vt[i + j * n] = a[i + j * m];
}
}
zungbr(false, n, n, m, vt, n, &taup);
info = zbdsqr(true, m, n, m, 0, s, &mut e, vt, n, u, m, &mut [], 0);
}
if iscl {
if anrm > bignum {
dlascl_g(bignum, anrm, s, minmn, minmn, 1);
} else if anrm < smlnum {
dlascl_g(smlnum, anrm, s, minmn, minmn, 1);
}
}
if info != 0 {
return Err(LapackError::Convergence);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Complex64, cpx};
fn vec_approx(got: &[f64], want: &[f64], tol: f64) {
assert_eq!(got.len(), want.len(), "vector lengths differ");
for i in 0..got.len() {
assert!((got[i] - want[i]).abs() <= tol, "entry {i}: {} vs {}", got[i], want[i]);
}
}
fn mat_approx(got: &[Complex64], want: &[Complex64], tol: f64) {
assert_eq!(got.len(), want.len(), "matrix lengths differ");
for i in 0..got.len() {
assert!((got[i] - want[i]).norm() <= tol, "entry {i}: {:?} vs {:?}", got[i], want[i]);
}
}
fn from_rows(rows: [[Complex64; 3]; 3]) -> Vec<Complex64> {
let mut a = vec![zero(); 9];
for i in 0..3 {
for j in 0..3 {
a[i + j * 3] = rows[i][j];
}
}
a
}
fn complex_sample(m: usize, n: usize, offset: f64) -> Vec<Complex64> {
let mut a = vec![zero(); m * n];
for i in 0..m {
for j in 0..n {
let re = (((i * 7 + j * 13 + 3) % 11) as f64) - 5.0 + offset;
let im = (((i * 5 + j * 17 + 7) % 9) as f64) - 4.0 - 0.5 * offset;
a[i + j * m] = Complex64::new(re, im);
}
}
a
}
fn run_svd(m: usize, n: usize, a: &[Complex64]) -> (Vec<f64>, Vec<Complex64>, Vec<Complex64>) {
let minmn = m.min(n);
let mut a_work = a.to_vec();
let mut s = vec![0.0; minmn];
let mut u = vec![zero(); m * m];
let mut vt = vec![zero(); n * n];
zgesvd(&mut a_work, m, n, &mut s, &mut u, &mut vt).unwrap();
(s, u, vt)
}
fn check_full_svd(m: usize, n: usize, a: &[Complex64], tol: f64) {
let minmn = m.min(n);
let mut a_work = a.to_vec();
let mut s = vec![0.0; minmn];
let mut u = vec![zero(); m * m];
let mut vt = vec![zero(); n * n];
zgesvd(&mut a_work, m, n, &mut s, &mut u, &mut vt).unwrap();
for k in 0..minmn {
assert!(s[k] >= 0.0, "s[{}] = {} is negative", k, s[k]);
}
for k in 1..minmn {
assert!(s[k - 1] >= s[k], "s is not descending at {}: {} < {}", k, s[k - 1], s[k]);
}
for i in 0..m {
for j in 0..m {
let mut acc = zero();
for k in 0..m {
acc += u[k + i * m].conj() * u[k + j * m];
}
let want = if i == j { one() } else { zero() };
assert!((acc - want).norm() < tol, "U not unitary at ({},{}): {:?}", i, j, acc);
}
}
for i in 0..n {
for j in 0..n {
let mut acc = zero();
for k in 0..n {
acc += vt[i + k * n] * vt[j + k * n].conj();
}
let want = if i == j { one() } else { zero() };
assert!((acc - want).norm() < tol, "V^H not unitary at ({},{}): {:?}", i, j, acc);
}
}
for i in 0..m {
for j in 0..n {
let mut acc = zero();
for k in 0..minmn {
acc += u[i + k * m] * Complex64::new(s[k], 0.0) * vt[k + j * n];
}
assert!(
(acc - a[i + j * m]).norm() < tol,
"reconstruction failed at ({},{}): {:?} vs {:?}",
i,
j,
acc,
a[i + j * m]
);
}
}
}
#[test]
fn zgesvd_reconstructs_for_all_shapes() {
for (m, n) in [(5, 3), (4, 4), (3, 5), (1, 1), (6, 2), (2, 6), (10, 7), (7, 10), (12, 12)] {
let a = complex_sample(m, n, 0.25);
check_full_svd(m, n, &a, 1e-12);
}
}
#[test]
fn zgesvd_handles_a_rank_deficient_matrix() {
let m = 4;
let n = 4;
let mut a = complex_sample(m, n, 0.5);
for j in 0..n {
a[2 + j * m] = a[0 + j * m];
a[3 + j * m] = a[1 + j * m];
}
check_full_svd(m, n, &a, 1e-12);
let mut a_work = a.clone();
let mut s = vec![0.0; 4];
let mut u = vec![zero(); 16];
let mut vt = vec![zero(); 16];
zgesvd(&mut a_work, m, n, &mut s, &mut u, &mut vt).unwrap();
assert!(s[3] < 1e-12 && s[2] < 1e-12, "expected two zero singular values, got {:?}", s);
}
#[test]
fn zgesvd_matches_the_real_svd_on_real_data() {
let m = 4;
let n = 3;
let mut re = vec![0.0; m * n];
for i in 0..m {
for j in 0..n {
re[i + j * m] = (((i * 7 + j * 13 + 3) % 11) as f64) - 5.0 + 0.25 * (i as f64) - 0.1 * (j as f64);
}
}
let a_complex = {
let mut a = vec![zero(); m * n];
for i in 0..m {
for j in 0..n {
a[i + j * m] = Complex64::new(re[i + j * m], 0.0);
}
}
a
};
let mut a_work = a_complex.clone();
let minmn = m.min(n);
let mut s_z = vec![0.0; minmn];
let mut u_z = vec![zero(); m * m];
let mut vt_z = vec![zero(); n * n];
zgesvd(&mut a_work, m, n, &mut s_z, &mut u_z, &mut vt_z).unwrap();
let mut a_re = re.clone();
let mut s_d = vec![0.0; minmn];
let mut u_d = vec![0.0; m * m];
let mut vt_d = vec![0.0; n * n];
crate::lapack::svd::dgesvd(&mut a_re, m, n, &mut s_d, &mut u_d, &mut vt_d).unwrap();
vec_approx(&s_z, &s_d, 1e-13);
for i in 0..m * m {
assert!(u_z[i].im.abs() < 1e-13, "U has imaginary part at {}: {:?}", i, u_z[i]);
}
for i in 0..n * n {
assert!(vt_z[i].im.abs() < 1e-13, "VT has imaginary part at {}: {:?}", i, vt_z[i]);
}
check_full_svd(m, n, &a_complex, 1e-12);
}
#[test]
fn zgesvd_uses_genuinely_complex_data() {
let a = complex_sample(3, 3, 0.75);
let mut a_work = a.clone();
let mut s = vec![0.0; 3];
let mut u = vec![zero(); 9];
let mut vt = vec![zero(); 9];
zgesvd(&mut a_work, 3, 3, &mut s, &mut u, &mut vt).unwrap();
check_full_svd(3, 3, &a, 1e-12);
let mut re = vec![zero(); 9];
for i in 0..9 {
re[i] = Complex64::new(a[i].re, 0.0);
}
let mut re_work = re.clone();
let mut s_re = vec![0.0; 3];
let mut u_re = vec![zero(); 9];
let mut vt_re = vec![zero(); 9];
zgesvd(&mut re_work, 3, 3, &mut s_re, &mut u_re, &mut vt_re).unwrap();
assert!(
(s[0] - s_re[0]).abs() > 1e-6 || (s[2] - s_re[2]).abs() > 1e-6,
"expected the imaginary parts to change the singular values: {:?} vs {:?}",
s,
s_re
);
}
#[test]
fn zgesvd_fails_on_bad_shapes() {
let mut a = vec![zero(); 6];
let mut s = vec![0.0; 2];
let mut u = vec![zero(); 9];
let mut vt = vec![zero(); 4];
assert_eq!(zgesvd(&mut a, 3, 2, &mut s[0..1], &mut u, &mut vt), Err(LapackError::BadShape));
assert_eq!(zgesvd(&mut a, 3, 2, &mut s, &mut u[0..8], &mut vt), Err(LapackError::BadShape));
assert_eq!(zgesvd(&mut a, 3, 2, &mut s, &mut u, &mut vt[0..3]), Err(LapackError::BadShape));
assert_eq!(zgesvd(&mut a[0..5], 3, 2, &mut s, &mut u, &mut vt), Err(LapackError::BadShape));
}
#[test]
fn zgesvd_matches_the_ibm_reference_values() {
let a = from_rows([
[cpx!(1.0, 1.0), cpx!(2.0, -1.0), cpx!(3.0, 0.0)],
[cpx!(2.0, -1.0), cpx!(4.0, 1.0), cpx!(5.0, -1.0)],
[cpx!(3.0, 0.0), cpx!(5.0, -1.0), cpx!(6.0, 1.0)],
]);
let (s, u, vt) = run_svd(3, 3, &a);
vec_approx(&s, &[11.370686, 2.386257, 1.006620], 1e-6);
#[rustfmt::skip]
let u_ref = from_rows([
[cpx!(-0.3265, 0.0409), cpx!( 0.0558, 0.4814), cpx!( 0.3504, -0.7308)],
[cpx!(-0.5822, 0.0725), cpx!(-0.0823, -0.7730), cpx!( 0.1017, -0.2026)],
[cpx!(-0.7396, 0.0233), cpx!( 0.0036, 0.4009), cpx!(-0.2805, 0.4616)],
]);
#[rustfmt::skip]
let vt_ref = from_rows([
[cpx!(-0.3290, 0.0000), cpx!(-0.5867, -0.0004), cpx!(-0.7367, -0.0688)],
[cpx!( 0.4846, 0.0000), cpx!(-0.7774, -0.0071), cpx!( 0.3987, 0.0425)],
[cpx!(-0.8105, 0.0000), cpx!(-0.2267, -0.0041), cpx!( 0.5375, 0.0533)],
]);
mat_approx(&u, &u_ref, 1e-4);
mat_approx(&vt, &vt_ref, 1e-4);
check_full_svd(3, 3, &a, 1e-12);
}
#[test]
fn zgesvd_scales_extreme_matrices() {
let m = 4;
let n = 3;
let base = complex_sample(m, n, 0.25);
let (s_base, _, _) = run_svd(m, n, &base);
for factor in [1e-160, 1e160] {
let a: Vec<Complex64> = base.iter().map(|v| scale(*v, factor)).collect();
let (s, _, _) = run_svd(m, n, &a);
for k in 0..n.min(m) {
let want = s_base[k] * factor;
let rel = (s[k] - want).abs() / want.abs();
assert!(rel < 1e-13, "scaling by {} broke s[{}]: {} vs {}", factor, k, s[k], want);
}
}
}
#[test]
fn zbdsqr_covers_the_lower_form_and_the_vector_modes() {
let n = 4;
let d_in = [1.0, 0.0, 2.0, 3.0];
let e_in = [0.5, 0.5, 0.25, 0.0];
let mut d = d_in.to_vec();
let mut e = e_in.to_vec();
let mut vt: Vec<Complex64> = vec![];
let mut u: Vec<Complex64> = vec![];
let mut c: Vec<Complex64> = vec![];
let info = zbdsqr(false, n, 0, 0, 0, &mut d, &mut e, &mut vt, 1, &mut u, 1, &mut c, 1);
assert_eq!(info, 0);
for k in 0..n {
assert!(d[k] >= 0.0, "s[{k}] = {} is negative", d[k]);
}
for k in 1..n {
assert!(d[k - 1] >= d[k], "s is not descending: {d:?}");
}
assert!(d[n - 1] < 1e-14, "one singular value must be zero: {d:?}");
let mut d_lo = [1.0, 2.0, 3.0, 4.0];
let mut e_lo = [0.5, 0.25, 0.125, 0.0];
let mut vt = vec![zero(); 16];
let mut u = vec![zero(); 16];
for i in 0..4 {
vt[i + i * 4] = Complex64::new(1.0, 0.0);
u[i + i * 4] = Complex64::new(1.0, 0.0);
}
let info = zbdsqr(true, 4, 4, 4, 0, &mut d_lo, &mut e_lo, &mut vt, 4, &mut u, 4, &mut c, 1);
assert_eq!(info, 0);
for k in 1..4 {
assert!(d_lo[k - 1] >= d_lo[k], "lower: s not descending: {d_lo:?}");
}
let (mut d_up, mut e_up) = (d_in.to_vec(), e_in.to_vec());
let mut vt_up = vec![zero(); 16];
let mut u_up = vec![zero(); 16];
let info = zbdsqr(false, 4, 0, 0, 0, &mut d_up, &mut e_up, &mut vt_up, 1, &mut u_up, 1, &mut c, 1);
assert_eq!(info, 0);
let ncc = 2;
let mut c_in = vec![zero(); 4 * ncc];
for i in 0..4 {
for j in 0..ncc {
c_in[i + j * 4] = Complex64::new(1.0 + (i as f64) * 0.5, -(j as f64) * 0.25);
}
}
let mut c_out = c_in.clone();
let (mut d_c, mut e_c) = (d_in.to_vec(), e_in.to_vec());
let mut vt_c = vec![zero(); 16];
let mut u_c = vec![zero(); 16];
let info = zbdsqr(false, 4, 0, 0, ncc, &mut d_c, &mut e_c, &mut vt_c, 1, &mut u_c, 1, &mut c_out, 4);
assert_eq!(info, 0);
for i in 0..ncc {
for j in 0..ncc {
let mut got = zero();
let mut want = zero();
for k in 0..4 {
got += c_out[k + i * 4].conj() * c_out[k + j * 4];
want += c_in[k + i * 4].conj() * c_in[k + j * 4];
}
assert!(
(got - want).norm() <= 1e-12 * want.norm().max(1.0),
"inner product ({i},{j}) changed: {got:?} != {want:?}"
);
}
}
assert!(
c_out.iter().zip(c_in.iter()).any(|(a, b)| (a - b).norm() > 1e-14),
"the NCC > 0 pass left C unchanged"
);
let mut d = vec![2.5];
let mut e = vec![0.0];
let mut vt = vec![Complex64::new(1.0, 0.0)];
let mut u = vec![Complex64::new(1.0, 0.0)];
let info = zbdsqr(false, 1, 1, 1, 0, &mut d, &mut e, &mut vt, 1, &mut u, 1, &mut c, 1);
assert_eq!(info, 0);
assert_eq!(d[0], 2.5);
}
#[test]
fn zgebd2_and_zungbr_reconstruct_the_bidiagonal_form() {
let eps = f64::EPSILON;
let one = Complex64::new(1.0, 0.0);
for (m, n) in [(2usize, 2usize), (3, 2), (3, 3), (4, 3), (6, 4), (2, 3), (3, 4), (4, 6)] {
let a0 = complex_sample(m, n, 0.5);
let mut a = a0.clone();
let minmn = m.min(n);
let mut d = vec![0.0; minmn];
let mut e = vec![0.0; minmn];
let mut tauq = vec![zero(); minmn];
let mut taup = vec![zero(); minmn];
zgebd2(&mut a, m, m, n, &mut d, &mut e, &mut tauq, &mut taup);
let mut q = vec![zero(); m * m];
if m >= n {
for j in 0..n {
for i in j..m {
q[i + j * m] = a[i + j * m];
}
}
} else {
for j in 0..m {
for i in j..m {
q[i + j * m] = a[i + j * m];
}
}
}
zungbr(true, m, m, n, &mut q, m, &tauq);
let mut vt = vec![zero(); n * n];
if m >= n {
for j in 0..n {
for i in 0..=j {
vt[i + j * n] = a[i + j * m];
}
}
} else {
for j in 0..n {
for i in 0..=j.min(m - 1) {
vt[i + j * n] = a[i + j * m];
}
}
}
let kp = if m >= n { n } else { m };
zungbr(false, n, n, kp, &mut vt, n, &taup);
let mut b = vec![zero(); m * n];
for i in 0..minmn {
b[i + i * m] = Complex64::new(d[i], 0.0);
if i + 1 < minmn {
if m >= n {
b[i + (i + 1) * m] = Complex64::new(e[i], 0.0);
} else {
b[(i + 1) + i * m] = Complex64::new(e[i], 0.0);
}
}
}
let mut qb = vec![zero(); m * n];
for j in 0..n {
for i in 0..m {
let mut sum = zero();
for k in 0..m {
sum += q[i + k * m] * b[k + j * m];
}
qb[i + j * m] = sum;
}
}
let mut rec = vec![zero(); m * n];
for j in 0..n {
for i in 0..m {
let mut sum = zero();
for k in 0..n {
sum += qb[i + k * m] * vt[k + j * n];
}
rec[i + j * m] = sum;
}
}
let mut a_norm = 0.0f64;
for j in 0..n {
let mut s = 0.0;
for i in 0..m {
s += a0[i + j * m].norm();
}
a_norm = a_norm.max(s);
}
let a_norm = a_norm.max(f64::MIN_POSITIVE);
let resid: f64 = (0..m * n).map(|k| (a0[k] - rec[k]).norm()).sum();
let ratio = resid / (m.max(n) as f64 * a_norm * eps);
assert!(ratio <= 20.0, "{m}x{n}: zbdt01 reconstruction ratio = {ratio} exceeds 20");
for (name, x, dim) in [("Q", &q, m), ("P", &vt, n)] {
let mut orth = 0.0;
for i in 0..dim {
for j in 0..dim {
let mut dot = zero();
for k in 0..dim {
dot += x[k + i * dim].conj() * x[k + j * dim];
}
let want = if i == j { one } else { zero() };
orth += (dot - want).norm();
}
}
assert!(orth / (dim as f64 * eps) <= 20.0, "{m}x{n}: {name} is not unitary ({orth})");
}
}
}
}