use super::LapackError;
use super::auxiliary::{Laln2, dlaln2};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Eigen2 {
pub rt1r: f64,
pub rt1i: f64,
pub rt2r: f64,
pub rt2i: f64,
}
pub fn dlarfg(alpha: f64, x: &[f64]) -> (f64, f64, Vec<f64>) {
let n = x.len() + 1;
let mut v = vec![0.0; n];
if n <= 1 {
return (alpha, 0.0, v);
}
let mut xscale = 0.0;
for &xi in x {
let a = xi.abs();
if a > xscale {
xscale = a;
}
}
if xscale == 0.0 {
return (alpha, 0.0, v);
}
let ss: f64 = x.iter().map(|&xi| (xi / xscale) * (xi / xscale)).sum();
let mut xnorm = xscale * ss.sqrt();
let safmin = f64::MIN_POSITIVE / f64::EPSILON;
let mut alpha = alpha;
let mut beta = -alpha.signum() * f64::hypot(alpha, xnorm);
if beta.abs() < safmin {
let rsafmn = 1.0 / safmin;
let mut knt = 0usize;
let mut xw = x.to_vec();
loop {
knt += 1;
for xi in xw.iter_mut() {
*xi *= rsafmn;
}
alpha *= rsafmn;
let mut sc = 0.0f64;
for &xi in xw.iter() {
let a = xi.abs();
if a > sc {
sc = a;
}
}
xnorm = if sc == 0.0 {
0.0
} else {
let ss: f64 = xw.iter().map(|&xi| (xi / sc) * (xi / sc)).sum();
sc * ss.sqrt()
};
beta = -alpha.signum() * f64::hypot(alpha, xnorm);
if beta.abs() >= safmin || knt >= 20 {
break;
}
}
let tau = (beta - alpha) / beta;
let s = 1.0 / (alpha - beta);
for (i, xi) in xw.iter().enumerate() {
v[i + 1] = xi * s;
}
for _ in 0..knt {
beta *= safmin;
}
let _ = alpha;
v[0] = 1.0;
return (beta, tau, v);
}
let tau = (beta - alpha) / beta;
let s = 1.0 / (alpha - beta);
for (i, xi) in x.iter().enumerate() {
v[i + 1] = xi * s;
}
v[0] = 1.0;
(beta, tau, v)
}
#[allow(clippy::too_many_arguments)]
pub fn dlanv2(a: &mut f64, b: &mut f64, c: &mut f64, d: &mut f64, cs: &mut f64, sn: &mut f64) -> Eigen2 {
const MULTPL: f64 = 4.0;
let safmin = f64::MIN_POSITIVE;
let eps = f64::EPSILON;
let safmn2 = (safmin / eps).sqrt();
let safmx2 = 1.0 / safmn2;
let mut temp;
if *c == 0.0 {
*cs = 1.0;
*sn = 0.0;
} else if *b == 0.0 {
*cs = 0.0;
*sn = 1.0;
temp = *d;
*d = *a;
*a = temp;
*b = -*c;
*c = 0.0;
} else if (*a - *d) == 0.0 && b.signum() != c.signum() {
*cs = 1.0;
*sn = 0.0;
} else {
temp = *a - *d;
let mut p = 0.5 * temp;
let bcmax = b.abs().max(c.abs());
let bcmis = b.abs().min(c.abs()) * b.signum() * c.signum();
let scale = p.abs().max(bcmax);
let mut z = (p / scale) * p + (bcmax / scale) * bcmis;
if z >= MULTPL * eps {
z = p + z.sqrt() * scale.sqrt() * p.signum();
*a = *d + z;
*d -= (bcmax / z) * bcmis;
let tau = f64::hypot(*c, z);
*cs = z / tau;
*sn = *c / tau;
*b -= *c;
*c = 0.0;
} else {
let mut count = 0;
let mut sigma = *b + *c;
loop {
count += 1;
let sc = temp.abs().max(sigma.abs());
if sc >= safmx2 {
sigma *= safmn2;
temp *= safmn2;
} else if sc <= safmn2 {
sigma *= safmx2;
temp *= safmx2;
} else {
break;
}
if count > 20 {
break;
}
}
p = 0.5 * temp;
let tau = f64::hypot(sigma, temp);
*cs = (0.5 * (1.0 + sigma.abs() / tau)).sqrt();
*sn = -(p / (tau * *cs)) * sigma.signum();
let aa = *a * *cs + *b * *sn;
let bb = -*a * *sn + *b * *cs;
let cc = *c * *cs + *d * *sn;
let dd = -*c * *sn + *d * *cs;
*a = aa * *cs + cc * *sn;
*b = (bb * *cs) + (dd * *sn);
*c = -(aa * *sn) + (cc * *cs);
*d = -bb * *sn + dd * *cs;
temp = 0.5 * (*a + *d);
*a = temp;
*d = temp;
if *c != 0.0 {
if *b != 0.0 {
if b.signum() == c.signum() {
let sab = b.abs().sqrt();
let sac = c.abs().sqrt();
p = sab * sac * c.signum();
let tau = 1.0 / (b.abs() + c.abs()).sqrt();
*a = temp + p;
*d = temp - p;
*b -= *c;
*c = 0.0;
let cs1 = sab * tau;
let sn1 = sac * tau;
temp = *cs * cs1 - *sn * sn1;
*sn = *cs * sn1 + *sn * cs1;
*cs = temp;
}
} else {
*b = -*c;
*c = 0.0;
temp = *cs;
*cs = -*sn;
*sn = temp;
}
}
}
}
let rt1r = *a;
let rt2r = *d;
if *c == 0.0 {
Eigen2 {
rt1r,
rt1i: 0.0,
rt2r,
rt2i: 0.0,
}
} else {
let wi = b.abs().sqrt() * c.abs().sqrt();
Eigen2 {
rt1r,
rt1i: wi,
rt2r,
rt2i: -wi,
}
}
}
pub fn dgehrd(a: &mut [f64], n: usize, q: Option<&mut [f64]>) {
assert_eq!(a.len(), n * n, "dgehrd: a must be n*n");
if let Some(qq) = q.as_ref() {
assert_eq!(qq.len(), n * n, "dgehrd: q must be n*n");
}
if n < 3 {
if let Some(qq) = q {
qq.fill(0.0);
for i in 0..n {
qq[i + i * n] = 1.0;
}
}
return;
}
let mut vs: Vec<Option<(Vec<f64>, f64)>> = vec![None; n];
for i in 0..(n - 2) {
let alpha = a[(i + 1) + i * n];
let tail: Vec<f64> = ((i + 2)..n).map(|r| a[r + i * n]).collect();
let (beta, tau, v) = dlarfg(alpha, &tail);
a[(i + 1) + i * n] = beta;
for r in (i + 2)..n {
a[r + i * n] = 0.0;
}
if tau == 0.0 {
continue;
}
vs[i] = Some((v.clone(), tau));
let m = n - i - 1;
let mut w = vec![0.0; n];
for r in 0..n {
let mut acc = 0.0;
for l in 0..m {
acc += a[r + (i + 1 + l) * n] * v[l];
}
w[r] = acc;
}
for r in 0..n {
for l in 0..m {
a[r + (i + 1 + l) * n] -= tau * w[r] * v[l];
}
}
for j in (i + 1)..n {
let mut acc = 0.0;
for l in 0..m {
acc += v[l] * a[(i + 1 + l) + j * n];
}
for l in 0..m {
a[(i + 1 + l) + j * n] -= tau * v[l] * acc;
}
}
}
if let Some(qq) = q {
qq.fill(0.0);
for i in 0..n {
qq[i + i * n] = 1.0;
}
for i in (0..(n - 2)).rev() {
let (v, tau) = match &vs[i] {
Some((v, tau)) => (v.as_slice(), *tau),
None => continue,
};
let m = v.len();
let base = i + 1;
let mut col = vec![0.0; m];
for j in 0..n {
for r in 0..m {
col[r] = qq[(base + r) + j * n];
}
let mut acc = 0.0;
for r in 0..m {
acc += v[r] * col[r];
}
let w = tau * acc;
for r in 0..m {
qq[(base + r) + j * n] = col[r] - w * v[r];
}
}
}
}
}
fn dlahqr(n: usize, ilo: usize, ihi: usize, h: &mut [f64], ldh: usize, wr: &mut [f64], wi: &mut [f64], wantz: bool, z: &mut [f64]) -> usize {
const KEXSH: usize = 10;
const DAT1: f64 = 3.0 / 4.0;
const DAT2: f64 = -0.4375;
if n == 0 {
return 0;
}
if ilo == ihi {
wr[ilo] = h[ilo + ilo * ldh];
wi[ilo] = 0.0;
return 0;
}
let mut j = ilo;
while j + 3 <= ihi {
h[(j + 2) + j * ldh] = 0.0;
h[(j + 3) + j * ldh] = 0.0;
j += 1;
}
if ilo + 2 <= ihi {
h[ihi + (ihi - 2) * ldh] = 0.0;
}
let nh = ihi - ilo + 1;
let safmin = f64::MIN_POSITIVE;
let ulp = f64::EPSILON;
let smlnum = safmin * (nh as f64 / ulp);
let wantt = true;
let mut i1 = 0usize;
let mut i2 = n - 1;
let itmax = 30 * nh.max(10);
let mut kdefl: usize = 0;
let mut i = ihi;
loop {
if i < ilo {
return 0;
}
let mut l = ilo;
let mut split_off = false;
let mut iters = 0usize;
while iters <= itmax {
iters += 1;
let mut k = i;
while k > l {
if h[k + (k - 1) * ldh].abs() <= smlnum {
break;
}
let mut tst = h[(k - 1) + (k - 1) * ldh].abs() + h[k + k * ldh].abs();
if tst == 0.0 {
if k >= ilo + 2 {
tst += h[(k - 1) + (k - 2) * ldh].abs();
}
if k < ihi {
tst += h[(k + 1) + k * ldh].abs();
}
}
if h[k + (k - 1) * ldh].abs() <= ulp * tst {
let ab = h[k + (k - 1) * ldh].abs().max(h[(k - 1) + k * ldh].abs());
let ba = h[k + (k - 1) * ldh].abs().min(h[(k - 1) + k * ldh].abs());
let aa = h[k + k * ldh].abs().max((h[(k - 1) + (k - 1) * ldh] - h[k + k * ldh]).abs());
let bb = h[k + k * ldh].abs().min((h[(k - 1) + (k - 1) * ldh] - h[k + k * ldh]).abs());
let s = aa + ab;
if ba * (ab / s) <= smlnum.max(ulp * (bb * (aa / s))) {
break;
}
}
k -= 1;
}
l = k;
if l > ilo {
h[l + (l - 1) * ldh] = 0.0;
}
if l + 1 >= i {
split_off = true;
break;
}
kdefl += 1;
if !wantt {
i1 = l;
i2 = i;
}
let (mut h11, mut h12, mut h21, mut h22);
if kdefl % (2 * KEXSH) == 0 {
let s = h[i + (i - 1) * ldh].abs() + h[(i - 1) + (i - 2) * ldh].abs();
h11 = DAT1 * s + h[i + i * ldh];
h12 = DAT2 * s;
h21 = s;
h22 = h11;
} else if kdefl % KEXSH == 0 {
let s = h[(l + 1) + l * ldh].abs() + h[(l + 2) + (l + 1) * ldh].abs();
h11 = DAT1 * s + h[l + l * ldh];
h12 = DAT2 * s;
h21 = s;
h22 = h11;
} else {
h11 = h[(i - 1) + (i - 1) * ldh];
h21 = h[i + (i - 1) * ldh];
h12 = h[(i - 1) + i * ldh];
h22 = h[i + i * ldh];
}
let s = h11.abs() + h12.abs() + h21.abs() + h22.abs();
let mut rt1r = 0.0;
let mut rt1i = 0.0;
let mut rt2r = 0.0;
let mut rt2i = 0.0;
if s == 0.0 {
} else {
h11 /= s;
h21 /= s;
h12 /= s;
h22 /= s;
let tr = (h11 + h22) / 2.0;
let det = (h11 - tr) * (h22 - tr) - h12 * h21;
let rtdisc = det.abs().sqrt();
if det >= 0.0 {
rt1r = tr * s;
rt2r = rt1r;
rt1i = rtdisc * s;
rt2i = -rt1i;
} else {
rt1r = tr + rtdisc;
rt2r = tr - rtdisc;
if (rt1r - h22).abs() <= (rt2r - h22).abs() {
rt1r *= s;
rt2r = rt1r;
} else {
rt2r *= s;
rt1r = rt2r;
}
rt1i = 0.0;
rt2i = 0.0;
}
}
let mut m = i - 2;
let mut v = [0.0f64; 3];
loop {
let mut h21s = h[(m + 1) + m * ldh];
let sv = (h[m + m * ldh] - rt2r).abs() + rt2i.abs() + h21s.abs();
h21s = h[(m + 1) + m * ldh] / sv;
v[0] = h21s * h[m + (m + 1) * ldh] + (h[m + m * ldh] - rt1r) * ((h[m + m * ldh] - rt2r) / sv) - rt1i * (rt2i / sv);
v[1] = h21s * (h[m + m * ldh] + h[(m + 1) + (m + 1) * ldh] - rt1r - rt2r);
v[2] = h21s * h[(m + 2) + (m + 1) * ldh];
let sv = v[0].abs() + v[1].abs() + v[2].abs();
v[0] /= sv;
v[1] /= sv;
v[2] /= sv;
if m == l {
break;
}
if h[m + (m - 1) * ldh].abs() * (v[1].abs() + v[2].abs())
<= ulp * v[0].abs() * (h[(m - 1) + (m - 1) * ldh].abs() + h[m + m * ldh].abs() + h[(m + 1) + (m + 1) * ldh].abs())
{
break;
}
m -= 1;
}
for k in m..=(i - 1) {
let nr = 3.min(i - k + 1);
if k > m {
for t in 0..nr {
v[t] = h[(k + t) + (k - 1) * ldh];
}
}
let (beta, t1, vv) = dlarfg(v[0], &v[1..nr]);
v[..nr].copy_from_slice(&vv[..nr]);
if k > m {
h[k + (k - 1) * ldh] = beta;
h[(k + 1) + (k - 1) * ldh] = 0.0;
if k < i - 1 {
h[(k + 2) + (k - 1) * ldh] = 0.0;
}
} else if m > l {
h[k + (k - 1) * ldh] *= 1.0 - t1;
}
let v2 = v[1];
let t2 = t1 * v2;
if nr == 3 {
let v3 = v[2];
let t3 = t1 * v3;
for jj in k..=i2 {
let sum = h[k + jj * ldh] + v2 * h[(k + 1) + jj * ldh] + v3 * h[(k + 2) + jj * ldh];
h[k + jj * ldh] -= sum * t1;
h[(k + 1) + jj * ldh] -= sum * t2;
h[(k + 2) + jj * ldh] -= sum * t3;
}
for jj in i1..=(k + 3).min(i) {
let sum = h[jj + k * ldh] + v2 * h[jj + (k + 1) * ldh] + v3 * h[jj + (k + 2) * ldh];
h[jj + k * ldh] -= sum * t1;
h[jj + (k + 1) * ldh] -= sum * t2;
h[jj + (k + 2) * ldh] -= sum * t3;
}
if wantz {
for jj in ilo..=ihi {
let sum = z[jj + k * n] + v2 * z[jj + (k + 1) * n] + v3 * z[jj + (k + 2) * n];
z[jj + k * n] -= sum * t1;
z[jj + (k + 1) * n] -= sum * t2;
z[jj + (k + 2) * n] -= sum * t3;
}
}
} else if nr == 2 {
for jj in k..=i2 {
let sum = h[k + jj * ldh] + v2 * h[(k + 1) + jj * ldh];
h[k + jj * ldh] -= sum * t1;
h[(k + 1) + jj * ldh] -= sum * t2;
}
for jj in i1..=i {
let sum = h[jj + k * ldh] + v2 * h[jj + (k + 1) * ldh];
h[jj + k * ldh] -= sum * t1;
h[jj + (k + 1) * ldh] -= sum * t2;
}
if wantz {
for jj in ilo..=ihi {
let sum = z[jj + k * n] + v2 * z[jj + (k + 1) * n];
z[jj + k * n] -= sum * t1;
z[jj + (k + 1) * n] -= sum * t2;
}
}
}
}
}
if !split_off {
return i;
}
if l == i {
wr[i] = h[i + i * ldh];
wi[i] = 0.0;
} else if l + 1 == i {
let (mut a, mut b, mut c, mut d) = (h[(i - 1) + (i - 1) * ldh], h[(i - 1) + i * ldh], h[i + (i - 1) * ldh], h[i + i * ldh]);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
h[(i - 1) + (i - 1) * ldh] = a;
h[(i - 1) + i * ldh] = b;
h[i + (i - 1) * ldh] = c;
h[i + i * ldh] = d;
wr[i - 1] = e.rt1r;
wi[i - 1] = e.rt1i;
wr[i] = e.rt2r;
wi[i] = e.rt2i;
if wantt {
if i2 > i {
for jj in (i + 1)..=i2 {
let x = h[(i - 1) + jj * ldh];
let y = h[i + jj * ldh];
let temp = cs * x + sn * y;
h[i + jj * ldh] = cs * y - sn * x;
h[(i - 1) + jj * ldh] = temp;
}
}
let count = i - i1 - 1;
for r in i1..(i1 + count) {
let x = h[r + (i - 1) * ldh];
let y = h[r + i * ldh];
let temp = cs * x + sn * y;
h[r + i * ldh] = cs * y - sn * x;
h[r + (i - 1) * ldh] = temp;
}
}
if wantz {
for r in ilo..=ihi {
let x = z[r + (i - 1) * n];
let y = z[r + i * n];
let temp = cs * x + sn * y;
z[r + i * n] = cs * y - sn * x;
z[r + (i - 1) * n] = temp;
}
}
}
kdefl = 0;
if l == 0 {
return 0;
}
i = l - 1;
}
}
pub fn dhseqr(t: &mut [f64], wr: &mut [f64], wi: &mut [f64], n: usize) -> Result<(), LapackError> {
dhseqr_impl(t, wr, wi, None, n)
}
pub(crate) fn dhseqr_with_schur_vectors(t: &mut [f64], wr: &mut [f64], wi: &mut [f64], z: &mut [f64], n: usize) -> Result<(), LapackError> {
dhseqr_impl(t, wr, wi, Some(z), n)
}
fn dhseqr_impl(t: &mut [f64], wr: &mut [f64], wi: &mut [f64], z: Option<&mut [f64]>, n: usize) -> Result<(), LapackError> {
assert_eq!(t.len(), n * n, "dhseqr: t must be n*n");
assert_eq!(wr.len(), n, "dhseqr: wr must have n entries");
assert_eq!(wi.len(), n, "dhseqr: wi must have n entries");
if n == 0 {
return Ok(());
}
let info = match z {
Some(z) => {
assert_eq!(z.len(), n * n, "dhseqr: z must be n*n");
dlahqr(n, 0, n - 1, t, n, wr, wi, true, z)
}
None => dlahqr(n, 0, n - 1, t, n, wr, wi, false, &mut []),
};
if info != 0 {
return Err(LapackError::Convergence);
}
Ok(())
}
pub fn dlartg(f: f64, g: f64) -> (f64, f64, f64) {
let safmin = f64::MIN_POSITIVE;
let safmax = f64::MAX;
let rtmin = safmin.sqrt();
let rtmax = (safmax / 2.0).sqrt();
let f1 = f.abs();
let g1 = g.abs();
if g == 0.0 {
(1.0, 0.0, f)
} else if f == 0.0 {
(0.0, g.signum(), g1)
} else if f1 > rtmin && f1 < rtmax && g1 > rtmin && g1 < rtmax {
let d = (f * f + g * g).sqrt();
let c = f1 / d;
let r = d.copysign(f);
let s = g / r;
(c, s, r)
} else {
let u = safmax.min(safmin.max(f1.max(g1)));
let fs = f / u;
let gs = g / u;
let d = (fs * fs + gs * gs).sqrt();
let c = fs.abs() / d;
let mut r = d.copysign(f);
let s = gs / r;
r *= u;
(c, s, r)
}
}
#[allow(clippy::too_many_arguments)]
fn dlaln2_real(
na: usize,
nw: usize,
smin: f64,
a11: f64,
a21: f64,
a12: f64,
a22: f64,
b11: f64,
b21: f64,
b12: f64,
b22: f64,
wr: f64,
wi: f64,
) -> Laln2 {
dlaln2(false, na, nw, smin, 1.0, a11, a21, a12, a22, 1.0, 1.0, b11, b21, b12, b22, wr, wi)
}
fn tget(t: &[f64], n: usize, i: usize, j: usize) -> f64 {
t[(i - 1) + (j - 1) * n]
}
fn vget(v: &[f64], n: usize, i: usize, j: usize) -> f64 {
v[(i - 1) + (j - 1) * n]
}
fn vset(v: &mut [f64], n: usize, i: usize, j: usize, x: f64) {
v[(i - 1) + (j - 1) * n] = x;
}
#[allow(unused_assignments)]
fn dtrevc_right(t: &[f64], vr: &mut [f64], n: usize) {
let unfl = f64::MIN_POSITIVE;
let ulp = f64::EPSILON;
let smlnum = unfl * (n as f64 / ulp);
let bignum = (1.0 - ulp) / smlnum;
let mut colnorm = vec![0.0; n];
for j in 2..=n {
let mut s = 0.0;
for i in 1..j {
s += tget(t, n, i, j).abs();
}
colnorm[j - 1] = s;
}
let mut work1 = vec![0.0; n];
let mut work2 = vec![0.0; n];
let mut y1 = vec![0.0; n];
let mut y2 = vec![0.0; n];
let mut ip = 0i32;
let mut ki = n; while ki >= 1 {
if ip != 1 {
if ki == 1 || tget(t, n, ki, ki - 1) == 0.0 {
ip = 0;
} else {
ip = -1;
}
let wr = tget(t, n, ki, ki);
let wi = if ip != 0 {
tget(t, n, ki, ki - 1).abs().sqrt() * tget(t, n, ki - 1, ki).abs().sqrt()
} else {
0.0
};
let smin = (ulp * (wr.abs() + wi.abs())).max(smlnum);
if ip == 0 {
work1.fill(0.0);
work1[ki - 1] = 1.0;
for k in 1..ki {
work1[k - 1] = -tget(t, n, k, ki);
}
let mut jnxt: isize = ki as isize - 1;
let mut j: isize = ki as isize - 1;
while j >= 1 {
if j <= jnxt {
let ju = j as usize;
let mut j1 = j;
let j2 = j;
jnxt = j - 1;
if j > 1 && tget(t, n, ju, ju - 1) != 0.0 {
j1 = j - 1;
jnxt = j - 2;
}
if j1 == j2 {
let r = dlaln2_real(1, 1, smin, tget(t, n, ju, ju), 0.0, 0.0, 0.0, work1[ju - 1], 0.0, 0.0, 0.0, wr, 0.0);
let mut x11 = r.x11;
let mut scale = r.scale;
if r.xnorm > 1.0 && colnorm[ju - 1] > bignum / r.xnorm {
x11 /= r.xnorm;
scale /= r.xnorm;
}
if scale != 1.0 {
for l in 0..ki {
work1[l] *= scale;
}
}
work1[ju - 1] = x11;
for l in 1..ju {
work1[l - 1] -= x11 * tget(t, n, l, ju);
}
} else {
let r = dlaln2_real(
2,
1,
smin,
tget(t, n, ju - 1, ju - 1),
tget(t, n, ju, ju - 1),
tget(t, n, ju - 1, ju),
tget(t, n, ju, ju),
work1[ju - 2],
work1[ju - 1],
0.0,
0.0,
wr,
0.0,
);
let mut x11 = r.x11;
let mut x21 = r.x21;
let mut scale = r.scale;
if r.xnorm > 1.0 {
let beta = colnorm[ju - 2].max(colnorm[ju - 1]);
if beta > bignum / r.xnorm {
x11 /= r.xnorm;
x21 /= r.xnorm;
scale /= r.xnorm;
}
}
if scale != 1.0 {
for l in 0..ki {
work1[l] *= scale;
}
}
work1[ju - 2] = x11;
work1[ju - 1] = x21;
for l in 1..(ju - 1) {
work1[l - 1] -= x11 * tget(t, n, l, ju - 1);
}
for l in 1..(ju - 1) {
work1[l - 1] -= x21 * tget(t, n, l, ju);
}
}
}
j -= 1;
}
if ki > 1 {
for i in 1..=n {
let mut s = work1[ki - 1] * vget(vr, n, i, ki);
for l in 1..ki {
s += work1[l - 1] * vget(vr, n, i, l);
}
y1[i - 1] = s;
}
for i in 1..=n {
vset(vr, n, i, ki, y1[i - 1]);
}
}
let ii = crate::blas::idamax(&vr[(ki - 1) * n..ki * n]);
let remax = 1.0 / vr[ii + (ki - 1) * n].abs();
for i in 0..n {
vr[i + (ki - 1) * n] *= remax;
}
} else {
if tget(t, n, ki - 1, ki).abs() >= tget(t, n, ki, ki - 1).abs() {
work1[ki - 2] = 1.0;
work2[ki - 1] = wi / tget(t, n, ki - 1, ki);
} else {
work1[ki - 2] = -wi / tget(t, n, ki, ki - 1);
work2[ki - 1] = 1.0;
}
work1[ki - 1] = 0.0;
work2[ki - 2] = 0.0;
for k in 1..=(ki - 2) {
work1[k - 1] = -work1[ki - 2] * tget(t, n, k, ki - 1);
work2[k - 1] = -work2[ki - 1] * tget(t, n, k, ki);
}
let mut jnxt: isize = ki as isize - 2;
let mut j: isize = ki as isize - 2;
while j >= 1 {
if j <= jnxt {
let ju = j as usize;
let mut j1 = j;
let j2 = j;
jnxt = j - 1;
if j > 1 && tget(t, n, ju, ju - 1) != 0.0 {
j1 = j - 1;
jnxt = j - 2;
}
if j1 == j2 {
let r = dlaln2_real(
1,
2,
smin,
tget(t, n, ju, ju),
0.0,
0.0,
0.0,
work1[ju - 1],
0.0,
work2[ju - 1],
0.0,
wr,
wi,
);
let mut x11 = r.x11;
let mut x12 = r.x12;
let mut scale = r.scale;
if r.xnorm > 1.0 && colnorm[ju - 1] > bignum / r.xnorm {
x11 /= r.xnorm;
x12 /= r.xnorm;
scale /= r.xnorm;
}
if scale != 1.0 {
for l in 0..ki {
work1[l] *= scale;
work2[l] *= scale;
}
}
work1[ju - 1] = x11;
work2[ju - 1] = x12;
for l in 1..ju {
work1[l - 1] -= x11 * tget(t, n, l, ju);
}
for l in 1..ju {
work2[l - 1] -= x12 * tget(t, n, l, ju);
}
} else {
let r = dlaln2_real(
2,
2,
smin,
tget(t, n, ju - 1, ju - 1),
tget(t, n, ju, ju - 1),
tget(t, n, ju - 1, ju),
tget(t, n, ju, ju),
work1[ju - 2],
work1[ju - 1],
work2[ju - 2],
work2[ju - 1],
wr,
wi,
);
let mut x11 = r.x11;
let mut x21 = r.x21;
let mut x12 = r.x12;
let mut x22 = r.x22;
let mut scale = r.scale;
if r.xnorm > 1.0 {
let beta = colnorm[ju - 2].max(colnorm[ju - 1]);
if beta > bignum / r.xnorm {
let rec = 1.0 / r.xnorm;
x11 *= rec;
x12 *= rec;
x21 *= rec;
x22 *= rec;
scale *= rec;
}
}
if scale != 1.0 {
for l in 0..ki {
work1[l] *= scale;
work2[l] *= scale;
}
}
work1[ju - 2] = x11;
work1[ju - 1] = x21;
work2[ju - 2] = x12;
work2[ju - 1] = x22;
for l in 1..(ju - 1) {
work1[l - 1] -= x11 * tget(t, n, l, ju - 1);
}
for l in 1..(ju - 1) {
work1[l - 1] -= x21 * tget(t, n, l, ju);
}
for l in 1..(ju - 1) {
work2[l - 1] -= x12 * tget(t, n, l, ju - 1);
}
for l in 1..(ju - 1) {
work2[l - 1] -= x22 * tget(t, n, l, ju);
}
}
}
j -= 1;
}
for i in 1..=n {
let mut s1 = work1[ki - 2] * vget(vr, n, i, ki - 1);
let mut s2 = work2[ki - 1] * vget(vr, n, i, ki);
for l in 1..(ki - 1) {
s1 += work1[l - 1] * vget(vr, n, i, l);
s2 += work2[l - 1] * vget(vr, n, i, l);
}
y1[i - 1] = s1;
y2[i - 1] = s2;
}
for i in 0..n {
vr[i + (ki - 2) * n] = y1[i];
vr[i + (ki - 1) * n] = y2[i];
}
let mut emax = 0.0f64;
for i in 0..n {
emax = emax.max(vr[i + (ki - 2) * n].abs() + vr[i + (ki - 1) * n].abs());
}
let remax = 1.0 / emax;
for i in 0..n {
vr[i + (ki - 2) * n] *= remax;
vr[i + (ki - 1) * n] *= remax;
}
}
}
if ip == 1 {
ip = 0;
} else if ip == -1 {
ip = 1;
}
ki -= 1;
}
}
fn column_norm2(v: &[f64], n: usize, j0: usize) -> f64 {
crate::blas::dnrm2(&v[j0 * n..(j0 + 1) * n])
}
fn dgebal(a: &mut [f64], n: usize) -> (usize, usize, Vec<f64>) {
const SCLFAC: f64 = 2.0;
const FACTOR: f64 = 0.95;
let mut scale = vec![1.0f64; n];
if n == 0 {
return (0, 0, scale);
}
let mut k = 0usize;
let mut l = n - 1;
let mut noconv = true;
while noconv {
noconv = false;
let mut i = l as isize;
while i >= 0 {
let iu = i as usize;
let mut canswap = true;
for j in 0..=l {
if iu != j && a[iu + j * n] != 0.0 {
canswap = false;
break;
}
}
if canswap {
scale[l] = (iu + 1) as f64;
if iu != l {
for r in 0..=l {
a.swap(r + iu * n, r + l * n);
}
for c in k..n {
a.swap(iu + c * n, l + c * n);
}
}
noconv = true;
if l == 0 {
return (0, 0, scale);
}
l -= 1;
}
i -= 1;
}
}
noconv = true;
while noconv {
noconv = false;
let mut j = k;
while j <= l {
let mut canswap = true;
for i in k..=l {
if i != j && a[i + j * n] != 0.0 {
canswap = false;
break;
}
}
if canswap {
scale[k] = (j + 1) as f64;
if j != k {
for r in 0..=l {
a.swap(r + j * n, r + k * n);
}
for c in k..n {
a.swap(j + c * n, k + c * n);
}
}
noconv = true;
k += 1;
}
j += 1;
}
}
for i in k..=l {
scale[i] = 1.0;
}
let sfmin1 = f64::MIN_POSITIVE / f64::EPSILON;
let sfmax1 = 1.0 / sfmin1;
let sfmin2 = sfmin1 * SCLFAC;
let sfmax2 = 1.0 / sfmin2;
let mut noconv = true;
while noconv {
noconv = false;
for i in k..=l {
let col: Vec<f64> = (k..=l).map(|r| a[r + i * n]).collect();
let row: Vec<f64> = (k..=l).map(|c| a[i + c * n]).collect();
let mut c = crate::blas::dnrm2(&col);
let mut r = crate::blas::dnrm2(&row);
let col_full: Vec<f64> = (0..=l).map(|rr| a[rr + i * n]).collect();
let ica = crate::blas::idamax(&col_full);
let mut ca = a[ica + i * n].abs();
let row_full: Vec<f64> = (k..n).map(|cc| a[i + cc * n]).collect();
let ira = crate::blas::idamax(&row_full) + k;
let mut ra = a[i + ira * n].abs();
if c == 0.0 || r == 0.0 {
continue;
}
if (c + ca + r + ra).is_nan() {
continue;
}
let mut g = r / SCLFAC;
let mut f = 1.0f64;
let s = c + r;
while c < g && f.max(c).max(ca) < sfmax2 && r.min(g).min(ra) > sfmin2 {
f *= SCLFAC;
c *= SCLFAC;
ca *= SCLFAC;
r /= SCLFAC;
g /= SCLFAC;
ra /= SCLFAC;
}
g = c / SCLFAC;
while g >= r && r.max(ra) < sfmax2 && f.min(c).min(g).min(ca) > sfmin2 {
f /= SCLFAC;
c /= SCLFAC;
g /= SCLFAC;
ca /= SCLFAC;
r *= SCLFAC;
ra *= SCLFAC;
}
if c + r >= FACTOR * s {
continue;
}
if f < 1.0 && scale[i] < 1.0 {
if f * scale[i] <= sfmin1 {
continue;
}
}
if f > 1.0 && scale[i] > 1.0 {
if scale[i] >= sfmax1 / f {
continue;
}
}
let g2 = 1.0 / f;
scale[i] *= f;
noconv = true;
for cc in k..n {
a[i + cc * n] *= g2;
}
for rr in 0..=l {
a[rr + i * n] *= f;
}
}
}
(k, l, scale)
}
fn dgebak_right(v: &mut [f64], n: usize, ilo: usize, ihi: usize, scale: &[f64]) {
if n == 0 {
return;
}
if ilo != ihi {
for i in ilo..=ihi {
let s = scale[i];
for j in 0..n {
v[i + j * n] *= s;
}
}
}
for ii in 0..n {
let i = if ii < ilo { ilo - ii - 1 } else { ii };
if i >= ilo && i <= ihi {
continue;
}
let k = scale[i] as usize - 1;
if k == i {
continue;
}
for j in 0..n {
v.swap(i + j * n, k + j * n);
}
}
}
pub fn dgeev(v_real: &mut [f64], v_imag: &mut [f64], a: &mut [f64], n: usize) -> Result<(Vec<f64>, Vec<f64>), LapackError> {
assert_eq!(a.len(), n * n, "dgeev: a must be n*n");
assert_eq!(v_real.len(), n * n, "dgeev: v_real must be n*n");
assert_eq!(v_imag.len(), n * n, "dgeev: v_imag must be n*n");
let mut wr = vec![0.0; n];
let mut wi = vec![0.0; n];
if n == 0 {
return Ok((wr, wi));
}
let eps = f64::EPSILON;
let smlnum0 = f64::MIN_POSITIVE;
let smlnum = smlnum0.sqrt() / eps;
let bignum = 1.0 / smlnum;
let mut anrm = 0.0f64;
for x in a.iter() {
anrm = anrm.max(x.abs());
}
let mut cscale = 0.0f64;
let scalea = if anrm > 0.0 && anrm < smlnum {
cscale = smlnum;
true
} else if anrm > bignum {
cscale = bignum;
true
} else {
false
};
if scalea {
let f = cscale / anrm;
for x in a.iter_mut() {
*x *= f;
}
}
let (ilo, ihi, bal_scale) = dgebal(a, n);
let mut q = vec![0.0; n * n];
dgehrd(a, n, Some(&mut q));
dhseqr_with_schur_vectors(a, &mut wr, &mut wi, &mut q, n)?;
let mut vr = q;
dtrevc_right(a, &mut vr, n);
dgebak_right(&mut vr, n, ilo, ihi, &bal_scale);
let mut i0 = 0usize;
while i0 < n {
if wi[i0] == 0.0 {
let scl = 1.0 / column_norm2(&vr, n, i0);
for r in 0..n {
vr[r + i0 * n] *= scl;
}
i0 += 1;
} else if wi[i0] > 0.0 {
let scl = 1.0 / f64::hypot(column_norm2(&vr, n, i0), column_norm2(&vr, n, i0 + 1));
for r in 0..n {
vr[r + i0 * n] *= scl;
vr[r + (i0 + 1) * n] *= scl;
}
let mut k = 0usize;
let mut best = -1.0f64;
for r in 0..n {
let mag = vr[r + i0 * n] * vr[r + i0 * n] + vr[r + (i0 + 1) * n] * vr[r + (i0 + 1) * n];
if mag > best {
best = mag;
k = r;
}
}
let (cs, sn, _r) = dlartg(vr[k + i0 * n], vr[k + (i0 + 1) * n]);
for r in 0..n {
let x = vr[r + i0 * n];
let y = vr[r + (i0 + 1) * n];
vr[r + i0 * n] = cs * x + sn * y;
vr[r + (i0 + 1) * n] = cs * y - sn * x;
}
vr[k + (i0 + 1) * n] = 0.0;
i0 += 2;
} else {
i0 += 1;
}
}
if scalea {
let f = anrm / cscale;
for x in wr.iter_mut() {
*x *= f;
}
for x in wi.iter_mut() {
*x *= f;
}
}
for i in 0..n {
if wi[i] == 0.0 {
for r in 0..n {
v_real[r + i * n] = vr[r + i * n];
v_imag[r + i * n] = 0.0;
}
} else if wi[i] > 0.0 {
for r in 0..n {
let re = vr[r + i * n];
let im = vr[r + (i + 1) * n];
v_real[r + i * n] = re;
v_imag[r + i * n] = im;
v_real[r + (i + 1) * n] = re;
v_imag[r + (i + 1) * n] = -im;
}
}
}
Ok((wr, wi))
}
#[allow(unused_assignments)]
fn dtrevc_left(t: &[f64], vl: &mut [f64], n: usize) {
let unfl = f64::MIN_POSITIVE;
let ulp = f64::EPSILON;
let smlnum = unfl * (n as f64 / ulp);
let bignum = (1.0 - ulp) / smlnum;
let mut colnorm = vec![0.0; n];
for j in 2..=n {
let mut s = 0.0;
for i in 1..j {
s += tget(t, n, i, j).abs();
}
colnorm[j - 1] = s;
}
let mut w1 = vec![0.0; n];
let mut w2 = vec![0.0; n];
let mut y1 = vec![0.0; n];
let mut y2 = vec![0.0; n];
let mut ip = 0i32;
let mut ki = 1usize;
while ki <= n {
if ip == 1 {
ip = -1;
ki += 1;
continue;
}
if ki == n || tget(t, n, ki + 1, ki) == 0.0 {
ip = 0;
} else {
ip = 1;
}
let wr = tget(t, n, ki, ki);
let wi = if ip != 0 {
tget(t, n, ki, ki + 1).abs().sqrt() * tget(t, n, ki + 1, ki).abs().sqrt()
} else {
0.0
};
let smin = (ulp * (wr.abs() + wi.abs())).max(smlnum);
if ip == 0 {
w1.fill(0.0);
w1[ki - 1] = 1.0;
for k in (ki + 1)..=n {
w1[k - 1] = -tget(t, n, ki, k);
}
let mut vmax = 1.0;
let mut vcrit = bignum;
let mut jnxt = ki + 1;
let mut j = ki + 1;
while j <= n {
if j >= jnxt {
let j1 = j;
let mut j2 = j;
jnxt = j + 1;
if j < n && tget(t, n, j + 1, j) != 0.0 {
j2 = j + 1;
jnxt = j + 2;
}
if j1 == j2 {
if colnorm[j - 1] > vcrit {
let rec = 1.0 / vmax;
for l in (ki - 1)..n {
w1[l] *= rec;
}
vmax = 1.0;
vcrit = bignum;
}
let mut s = 0.0;
for l in (ki + 1)..j {
s += tget(t, n, l, j) * w1[l - 1];
}
w1[j - 1] -= s;
let r = dlaln2_real(1, 1, smin, tget(t, n, j, j), 0.0, 0.0, 0.0, w1[j - 1], 0.0, 0.0, 0.0, wr, 0.0);
if r.scale != 1.0 {
for l in (ki - 1)..n {
w1[l] *= r.scale;
}
}
w1[j - 1] = r.x11;
vmax = w1[j - 1].abs().max(vmax);
vcrit = bignum / vmax;
} else {
let beta = colnorm[j - 1].max(colnorm[j]);
if beta > vcrit {
let rec = 1.0 / vmax;
for l in (ki - 1)..n {
w1[l] *= rec;
}
vmax = 1.0;
vcrit = bignum;
}
let mut s = 0.0;
for l in (ki + 1)..j {
s += tget(t, n, l, j) * w1[l - 1];
}
w1[j - 1] -= s;
let mut s2 = 0.0;
for l in (ki + 1)..j {
s2 += tget(t, n, l, j + 1) * w1[l - 1];
}
w1[j] -= s2;
let r = dlaln2_real(
2,
1,
smin,
tget(t, n, j, j),
tget(t, n, j, j + 1),
tget(t, n, j + 1, j),
tget(t, n, j + 1, j + 1),
w1[j - 1],
w1[j],
0.0,
0.0,
wr,
0.0,
);
if r.scale != 1.0 {
for l in (ki - 1)..n {
w1[l] *= r.scale;
}
}
w1[j - 1] = r.x11;
w1[j] = r.x21;
vmax = w1[j - 1].abs().max(w1[j].abs()).max(vmax);
vcrit = bignum / vmax;
}
}
j += 1;
}
if ki < n {
for i in 1..=n {
let mut s = w1[ki - 1] * vget(vl, n, i, ki);
for l in (ki + 1)..=n {
s += w1[l - 1] * vget(vl, n, i, l);
}
y1[i - 1] = s;
}
for i in 1..=n {
vset(vl, n, i, ki, y1[i - 1]);
}
}
let ii = crate::blas::idamax(&vl[(ki - 1) * n..ki * n]);
let remax = 1.0 / vl[ii + (ki - 1) * n].abs();
for i in 0..n {
vl[i + (ki - 1) * n] *= remax;
}
} else {
if tget(t, n, ki, ki + 1).abs() >= tget(t, n, ki + 1, ki).abs() {
w1[ki - 1] = wi / tget(t, n, ki, ki + 1);
w2[ki] = 1.0;
} else {
w1[ki - 1] = 1.0;
w2[ki] = -wi / tget(t, n, ki + 1, ki);
}
w1[ki] = 0.0;
w2[ki - 1] = 0.0;
for k in (ki + 2)..=n {
w1[k - 1] = -w1[ki - 1] * tget(t, n, ki, k);
w2[k - 1] = -w2[ki] * tget(t, n, ki + 1, k);
}
let mut vmax = 1.0;
let mut vcrit = bignum;
let mut jnxt = ki + 2;
let mut j = ki + 2;
while j <= n {
if j >= jnxt {
let j1 = j;
let mut j2 = j;
jnxt = j + 1;
if j < n && tget(t, n, j + 1, j) != 0.0 {
j2 = j + 1;
jnxt = j + 2;
}
if j1 == j2 {
if colnorm[j - 1] > vcrit {
let rec = 1.0 / vmax;
for l in (ki - 1)..n {
w1[l] *= rec;
w2[l] *= rec;
}
vmax = 1.0;
vcrit = bignum;
}
let mut s1 = 0.0;
for l in (ki + 2)..j {
s1 += tget(t, n, l, j) * w1[l - 1];
}
w1[j - 1] -= s1;
let mut s2 = 0.0;
for l in (ki + 2)..j {
s2 += tget(t, n, l, j) * w2[l - 1];
}
w2[j - 1] -= s2;
let r = dlaln2_real(1, 2, smin, tget(t, n, j, j), 0.0, 0.0, 0.0, w1[j - 1], 0.0, w2[j - 1], 0.0, wr, -wi);
if r.scale != 1.0 {
for l in (ki - 1)..n {
w1[l] *= r.scale;
w2[l] *= r.scale;
}
}
w1[j - 1] = r.x11;
w2[j - 1] = r.x12;
vmax = w1[j - 1].abs().max(w2[j - 1].abs()).max(vmax);
vcrit = bignum / vmax;
} else {
let beta = colnorm[j - 1].max(colnorm[j]);
if beta > vcrit {
let rec = 1.0 / vmax;
for l in (ki - 1)..n {
w1[l] *= rec;
w2[l] *= rec;
}
vmax = 1.0;
vcrit = bignum;
}
let mut s1 = 0.0;
for l in (ki + 2)..j {
s1 += tget(t, n, l, j) * w1[l - 1];
}
w1[j - 1] -= s1;
let mut s2 = 0.0;
for l in (ki + 2)..j {
s2 += tget(t, n, l, j) * w2[l - 1];
}
w2[j - 1] -= s2;
let mut s3 = 0.0;
for l in (ki + 2)..j {
s3 += tget(t, n, l, j + 1) * w1[l - 1];
}
w1[j] -= s3;
let mut s4 = 0.0;
for l in (ki + 2)..j {
s4 += tget(t, n, l, j + 1) * w2[l - 1];
}
w2[j] -= s4;
let r = dlaln2_real(
2,
2,
smin,
tget(t, n, j, j),
tget(t, n, j, j + 1),
tget(t, n, j + 1, j),
tget(t, n, j + 1, j + 1),
w1[j - 1],
w1[j],
w2[j - 1],
w2[j],
wr,
-wi,
);
if r.scale != 1.0 {
for l in (ki - 1)..n {
w1[l] *= r.scale;
w2[l] *= r.scale;
}
}
w1[j - 1] = r.x11;
w2[j - 1] = r.x12;
w1[j] = r.x21;
w2[j] = r.x22;
vmax = w1[j - 1].abs().max(w2[j - 1].abs()).max(w1[j].abs()).max(w2[j].abs()).max(vmax);
vcrit = bignum / vmax;
}
}
j += 1;
}
if ki < n - 1 {
for i in 1..=n {
let mut s1 = w1[ki - 1] * vget(vl, n, i, ki);
let mut s2 = w2[ki] * vget(vl, n, i, ki + 1);
for l in (ki + 2)..=n {
s1 += w1[l - 1] * vget(vl, n, i, l);
s2 += w2[l - 1] * vget(vl, n, i, l);
}
y1[i - 1] = s1;
y2[i - 1] = s2;
}
for i in 0..n {
vl[i + (ki - 1) * n] = y1[i];
vl[i + ki * n] = y2[i];
}
} else {
for i in 0..n {
vl[i + (ki - 1) * n] *= w1[ki - 1];
vl[i + ki * n] *= w2[ki];
}
}
let mut emax = 0.0f64;
for i in 0..n {
emax = emax.max(vl[i + (ki - 1) * n].abs() + vl[i + ki * n].abs());
}
let remax = 1.0 / emax;
for i in 0..n {
vl[i + (ki - 1) * n] *= remax;
vl[i + ki * n] *= remax;
}
}
ki += 1;
}
}
fn dgebak_left(v: &mut [f64], n: usize, ilo: usize, ihi: usize, scale: &[f64]) {
if n == 0 {
return;
}
if ilo != ihi {
for i in ilo..=ihi {
let s = 1.0 / scale[i];
for j in 0..n {
v[i + j * n] *= s;
}
}
}
for ii in 0..n {
let i = if ii < ilo { ilo - ii - 1 } else { ii };
if i >= ilo && i <= ihi {
continue;
}
let k = scale[i] as usize - 1;
if k == i {
continue;
}
for j in 0..n {
v.swap(i + j * n, k + j * n);
}
}
}
fn dgeev_normalize_columns(v: &mut [f64], n: usize, wi: &[f64]) {
let mut k = 0usize;
while k < n {
if wi[k] == 0.0 {
let scl = 1.0 / column_norm2(v, n, k);
for r in 0..n {
v[r + k * n] *= scl;
}
k += 1;
} else if wi[k] > 0.0 {
let scl = 1.0 / f64::hypot(column_norm2(v, n, k), column_norm2(v, n, k + 1));
for r in 0..n {
v[r + k * n] *= scl;
v[r + (k + 1) * n] *= scl;
}
let mut kk = 0usize;
let mut best = -1.0f64;
for r in 0..n {
let mag = v[r + k * n] * v[r + k * n] + v[r + (k + 1) * n] * v[r + (k + 1) * n];
if mag > best {
best = mag;
kk = r;
}
}
let (cs, sn, _r) = dlartg(v[kk + k * n], v[kk + (k + 1) * n]);
for r in 0..n {
let x = v[r + k * n];
let y = v[r + (k + 1) * n];
v[r + k * n] = cs * x + sn * y;
v[r + (k + 1) * n] = cs * y - sn * x;
}
v[kk + (k + 1) * n] = 0.0;
k += 2;
} else {
k += 1;
}
}
}
fn dgeev_unpack(raw: &[f64], wi: &[f64], re: &mut [f64], im: &mut [f64], n: usize) {
for i in 0..n {
if wi[i] == 0.0 {
for r in 0..n {
re[r + i * n] = raw[r + i * n];
im[r + i * n] = 0.0;
}
} else if wi[i] > 0.0 {
for r in 0..n {
let a = raw[r + i * n];
let b = raw[r + (i + 1) * n];
re[r + i * n] = a;
im[r + i * n] = b;
re[r + (i + 1) * n] = a;
im[r + (i + 1) * n] = -b;
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn dgeev_lr(
u_real: &mut [f64],
u_imag: &mut [f64],
v_real: &mut [f64],
v_imag: &mut [f64],
a: &mut [f64],
n: usize,
) -> Result<(Vec<f64>, Vec<f64>), LapackError> {
assert_eq!(a.len(), n * n, "dgeev_lr: a must be n*n");
assert_eq!(u_real.len(), n * n, "dgeev_lr: u_real must be n*n");
assert_eq!(u_imag.len(), n * n, "dgeev_lr: u_imag must be n*n");
assert_eq!(v_real.len(), n * n, "dgeev_lr: v_real must be n*n");
assert_eq!(v_imag.len(), n * n, "dgeev_lr: v_imag must be n*n");
let mut wr = vec![0.0; n];
let mut wi = vec![0.0; n];
if n == 0 {
return Ok((wr, wi));
}
let eps = f64::EPSILON;
let smlnum0 = f64::MIN_POSITIVE;
let smlnum = smlnum0.sqrt() / eps;
let bignum = 1.0 / smlnum;
let mut anrm = 0.0f64;
for x in a.iter() {
anrm = anrm.max(x.abs());
}
let mut cscale = 0.0f64;
let scalea = if anrm > 0.0 && anrm < smlnum {
cscale = smlnum;
true
} else if anrm > bignum {
cscale = bignum;
true
} else {
false
};
if scalea {
let f = cscale / anrm;
for x in a.iter_mut() {
*x *= f;
}
}
let (ilo, ihi, bal_scale) = dgebal(a, n);
let mut q = vec![0.0; n * n];
dgehrd(a, n, Some(&mut q));
dhseqr_with_schur_vectors(a, &mut wr, &mut wi, &mut q, n)?;
let mut vl = q.clone();
let mut vr = q;
dtrevc_left(a, &mut vl, n);
dtrevc_right(a, &mut vr, n);
dgebak_left(&mut vl, n, ilo, ihi, &bal_scale);
dgebak_right(&mut vr, n, ilo, ihi, &bal_scale);
dgeev_normalize_columns(&mut vl, n, &wi);
dgeev_normalize_columns(&mut vr, n, &wi);
if scalea {
let f = anrm / cscale;
for x in wr.iter_mut() {
*x *= f;
}
for x in wi.iter_mut() {
*x *= f;
}
}
dgeev_unpack(&vl, &wi, u_real, u_imag, n);
dgeev_unpack(&vr, &wi, v_real, v_imag, n);
Ok((wr, wi))
}
#[cfg(test)]
mod tests {
use super::super::auxiliary::dladiv;
use super::*;
use crate::Complex64;
fn sample(n: usize) -> Vec<f64> {
let mut a = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
a[i + j * n] = if i == j {
(n as f64) + 1.0 + (i as f64) * 0.5
} else {
(((i * 7 + j * 13) % 11) as f64) - 5.0
};
}
}
a
}
fn norm_max(a: &[f64]) -> f64 {
a.iter().fold(0.0f64, |m, v| m.max(v.abs()))
}
fn mat_mul(a: &[f64], b: &[f64], n: usize) -> Vec<f64> {
let mut c = vec![0.0; n * n];
for j in 0..n {
for i in 0..n {
let mut s = 0.0;
for l in 0..n {
s += a[i + l * n] * b[l + j * n];
}
c[i + j * n] = s;
}
}
c
}
fn column_major(rows: &[f64], n: usize) -> Vec<f64> {
let mut a = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
a[i + j * n] = rows[i * n + j];
}
}
a
}
fn norm_1(a: &[f64], m: usize, n: usize) -> f64 {
let mut best = 0.0f64;
for j in 0..n {
let mut sum = 0.0;
for i in 0..m {
sum += a[i + j * m].abs();
}
best = best.max(sum);
}
best
}
fn transpose(a: &[f64], n: usize) -> Vec<f64> {
let mut t = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
t[i + j * n] = a[j + i * n];
}
}
t
}
#[test]
fn dlarfg_annihilates_the_tail() {
let x = [3.0, 4.0];
let (beta, tau, v) = dlarfg(x[0], &x[1..]);
assert!((beta.abs() - 5.0).abs() < 1e-15, "beta = {beta}");
let mut hx = x.to_vec();
let w: f64 = v.iter().zip(x.iter()).map(|(vi, xi)| vi * xi).sum::<f64>() * tau;
for i in 0..2 {
hx[i] -= w * v[i];
}
assert!((hx[0] - beta).abs() < 1e-14, "first component {} vs {beta}", hx[0]);
assert!(hx[1].abs() < 1e-14, "tail must be annihilated, got {}", hx[1]);
}
#[test]
fn dlarfg_of_a_zero_tail_is_the_identity() {
let (beta, tau, _v) = dlarfg(2.5, &[0.0, 0.0]);
assert_eq!(beta, 2.5);
assert_eq!(tau, 0.0);
}
#[test]
fn dlarfg_rescales_a_subnormal_tail() {
let tiny = 1e-310f64;
let (beta, tau, v) = dlarfg(tiny, &[tiny, -tiny]);
assert!(beta.is_finite() && beta != 0.0, "beta = {beta:e}");
assert!(tau.is_finite() && tau != 0.0, "tau = {tau:e}");
assert_eq!(v[0], 1.0);
let x = [tiny, tiny, -tiny];
let mut hx = x;
let w: f64 = v.iter().zip(x.iter()).map(|(vi, xi)| vi * xi).sum::<f64>() * tau;
for i in 0..3 {
hx[i] -= w * v[i];
}
assert!((hx[0] - beta).abs() <= 4.0 * f64::MIN_POSITIVE, "first component {} vs {beta:e}", hx[0]);
assert!(
hx[1].abs() <= 4.0 * f64::MIN_POSITIVE && hx[2].abs() <= 4.0 * f64::MIN_POSITIVE,
"tail not annihilated: {:?}",
&hx[1..]
);
}
#[test]
fn dlarfg_handles_a_tiny_vector() {
let tiny = 1e-300;
let (beta, tau, v) = dlarfg(tiny, &[tiny]);
assert!(beta != 0.0, "beta underflowed to zero");
assert!(tau != 0.0, "tau underflowed to zero");
let x = [tiny, tiny];
let mut hx = x;
let w: f64 = v.iter().zip(x.iter()).map(|(vi, xi)| vi * xi).sum::<f64>() * tau;
for i in 0..2 {
hx[i] -= w * v[i];
}
assert!((hx[0] - beta).abs() <= tiny.abs(), "first component {} vs {beta}", hx[0]);
assert!(hx[1].abs() <= tiny.abs(), "tail must be annihilated, got {}", hx[1]);
}
#[test]
fn dgehrd_produces_hessenberg_and_an_orthogonal_q() {
for n in [1usize, 2, 3, 5, 8, 20] {
let orig = sample(n);
let mut h = orig.clone();
let mut q = vec![0.0; n * n];
dgehrd(&mut h, n, Some(&mut q));
for j in 0..n {
for i in (j + 2)..n {
assert!(
h[i + j * n].abs() < 1e-13,
"n={n}: H({i},{j}) = {} is below the subdiagonal",
h[i + j * n]
);
}
}
let qh = mat_mul(&q, &h, n);
let qhqt = mat_mul(&qh, &transpose(&q, n), n);
let (mut diff, mut qtq_diff) = (0.0, 0.0);
for idx in 0..n * n {
diff += (orig[idx] - qhqt[idx]).abs();
let (i, j) = (idx % n, idx / n);
let mut dot = 0.0;
for l in 0..n {
dot += q[l + i * n] * q[l + j * n];
}
qtq_diff += (dot - if i == j { 1.0 } else { 0.0 }).abs();
}
let eps = f64::EPSILON;
let ratio1 = diff / (norm_1(&orig, n, n).max(f64::MIN_POSITIVE) * n as f64 * eps);
let ratio2 = qtq_diff / (n as f64 * eps);
assert!(ratio1 <= 20.0, "n={n}: dhst01 ratio 1 = {ratio1} exceeds 20");
assert!(ratio2 <= 20.0, "n={n}: dhst01 ratio 2 = {ratio2} exceeds 20");
}
}
const DBAL_CASES: &[(usize, &[f64], usize, usize, &[f64], &[f64])] = &[
(
5,
&[
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0,
],
1,
1,
&[
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0,
],
&[1.0, 2.0, 3.0, 4.0, 5.0],
),
(
5,
&[
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0,
],
1,
1,
&[
5.0, 4.0, 3.0, 2.0, 1.0, 0.0, 4.0, 3.0, 2.0, 1.0, 0.0, 0.0, 3.0, 2.0, 1.0, 0.0, 0.0, 0.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
],
&[1.0, 2.0, 3.0, 2.0, 1.0],
),
(
5,
&[
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0,
],
1,
1,
&[
1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
],
&[1.0, 2.0, 3.0, 2.0, 1.0],
),
(
4,
&[0.0, 2.0, 0.1, 0.0, 2.0, 0.0, 0.0, 0.1, 100.0, 0.0, 0.0, 2.0, 0.0, 100.0, 2.0, 0.0],
1,
4,
&[0.0, 2.0, 3.2, 0.0, 2.0, 0.0, 0.0, 3.2, 3.125, 0.0, 0.0, 2.0, 0.0, 3.125, 2.0, 0.0],
&[0.0625, 0.0625, 2.0, 2.0],
),
(
6,
&[
2.0, 0.0, 0.0, 0.0, 0.0, 1024.0, 0.0, 0.0, 0.0, 0.0, 0.0, 128.0, 0.0, 2.0, 3000.0, 0.0, 0.0, 2.0, 128.0, 4.0, 0.004, 5.0, 600.0, 8.0,
0.0, 0.0, 0.0, 0.0, 0.002, 2.0, 8.0, 8192.0, 0.0, 0.0, 0.0, 2.0,
],
4,
6,
&[
5.0, 600.0, 0.004, 1024.0, 0.5, 8.0, 0.0, 0.002, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 3000.0, 0.0, 0.25, 2.0, 0.0, 0.0, 0.0, 2.0, 0.0,
128.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1024.0, 0.0, 0.0, 0.0, 64.0, 1024.0, 2.0,
],
&[4.0, 5.0, 3.0, 8.0, 0.125, 1.0],
),
(
5,
&[
1.0, 0.0, 0.0, 0.0, 8.0, 0.0, 2.0, 8192.0, 2.0, 4.0, 0.00025, 0.000125, 4.0, 0.0, 64.0, 0.0, 2.0, 1024.0, 4.0, 8.0, 0.0, 8192.0, 0.0,
0.0, 8.0,
],
1,
5,
&[
1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 2.0, 1024.0, 16.0, 16.0, 0.032, 0.001, 4.0, 0.0, 2048.0, 0.0, 0.25, 16.0, 4.0, 4.0, 0.0, 2048.0, 0.0,
0.0, 8.0,
],
&[8.0, 0.5, 0.0625, 4.0, 2.0],
),
(
4,
&[
1.0, 1000000.0, 1000000.0, 1000000.0, -2000000.0, 3.0, 2e-06, 3e-06, -3000000.0, 0.0, 1e-06, 2.0, 1000000.0, 0.0, 3e-06, 4000000.0,
],
1,
4,
&[
1.0, 1000000.0, 2000000.0, 1000000.0, -2000000.0, 3.0, 4e-06, 3e-06, -1500000.0, 0.0, 1e-06, 1.0, 1000000.0, 0.0, 6e-06, 4000000.0,
],
&[1.0, 1.0, 2.0, 1.0],
),
(
4,
&[
1.0, 10000.0, 10000.0, 10000.0, -20000.0, 3.0, 0.002, 0.003, 0.0, 2.0, 0.0, -30000.0, 0.0, 0.0, 10000.0, 0.0,
],
1,
4,
&[
1.0, 10000.0, 10000.0, 5000.0, -20000.0, 3.0, 0.002, 0.0015, 0.0, 2.0, 0.0, -15000.0, 0.0, 0.0, 20000.0, 0.0,
],
&[1.0, 1.0, 1.0, 0.5],
),
(
5,
&[
1.0, 512.0, 4096.0, 32768.0, 262144.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 8.0,
0.0,
],
1,
5,
&[
1.0, 32.0, 32.0, 32.0, 32.0, 128.0, 0.0, 0.0, 0.0, 0.0, 0.0, 64.0, 0.0, 0.0, 0.0, 0.0, 0.0, 64.0, 0.0, 0.0, 0.0, 0.0, 0.0, 64.0, 0.0,
],
&[256.0, 16.0, 2.0, 0.25, 0.03125],
),
(
6,
&[
1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0,
0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0,
],
2,
5,
&[
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
],
&[3.0, 1.0, 1.0, 1.0, 1.0, 4.0],
),
(
7,
&[
6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 4.0, 0.0, 0.00025, 0.0125, 0.02, 0.125, 1.0, 128.0, 64.0, 0.0, 0.0, -2.0, 16.0, 0.0, 16384.0,
0.0, 1.0, -400.0, 256.0, -4000.0, -2.0, -256.0, 0.0, 0.0125, 2.0, 2.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 8.0, 0.0, 0.004,
0.125, -0.2, 3.0,
],
2,
5,
&[
64.0, 1.0, 0.5, 0.0, 0.0, 1.0, -2.0, 0.0, 4.0, 0.5, 1.024, 0.8, 0.0, 2.56, 0.0, 2.0, 3.0, 4.096, 2.0, 0.0, -6.4, 0.0, 4.0, -3.90625,
1.0, -6.25, 0.0, 8.0, 0.0, -4.0, 2.0, 0.8, 2.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 6.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
&[3.0, 0.0078125, 0.03125, 32.0, 0.5, 1.0, 6.0],
),
(
5,
&[
1000.0, 2.0, 3.0, 4.0, 500000.0, 9.0, 0.0, 0.0002, 1.0, 3.0, 0.0, -300.0, 2.0, 1.0, 1.0, 9.0, 0.002, 1.0, 1.0, -1000.0, 6.0, 200.0,
1.0, 600.0, 3.0,
],
1,
5,
&[
1000.0, 0.03125, 0.375, 0.03125, 1953.125, 576.0, 0.0, 0.0016, 0.5, 0.75, 0.0, -37.5, 2.0, 0.0625, 0.03125, 1152.0, 0.004, 16.0, 1.0,
-500.0, 1536.0, 800.0, 32.0, 1200.0, 3.0,
],
&[32.0, 0.5, 4.0, 0.25, 0.125],
),
(
6,
&[
1.0, 1e+120, 0.0, 0.0, 0.0, 0.0, 1e-120, 1.0, 1e+120, 0.0, 0.0, 0.0, 0.0, 1e-120, 1.0, 1e+120, 0.0, 0.0, 0.0, 0.0, 1e-120, 1.0,
1e+120, 0.0, 0.0, 0.0, 0.0, 1e-120, 1.0, 1e+120, 0.0, 0.0, 0.0, 0.0, 1e-120, 1.0,
],
1,
6,
&[
1.0,
6344.854593289123,
0.0,
0.0,
0.0,
0.0,
0.0001576080247855779,
1.0,
6344.854593289123,
0.0,
0.0,
0.0,
0.0,
0.0001576080247855779,
1.0,
3172.4272966445615,
0.0,
0.0,
0.0,
0.0,
0.0003152160495711558,
1.0,
1586.2136483222807,
0.0,
0.0,
0.0,
0.0,
0.0006304320991423117,
1.0,
793.1068241611404,
0.0,
0.0,
0.0,
0.0,
0.0012608641982846233,
1.0,
],
&[
2.4948003869184e+291,
1.582914569427869e+175,
1.004336277661869e+59,
3.1861838222649046e-58,
5.053968264940244e-175,
4.008336720017946e-292,
],
),
];
const DBAK_CASES: &[(usize, usize, usize, &[f64], &[f64], &[f64])] = &[
(
5,
1,
1,
&[1.0, 2.0, 3.0, 4.0, 5.0],
&[
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
],
&[
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
],
),
(
5,
1,
1,
&[1.0, 2.0, 3.0, 2.0, 1.0],
&[
1.0, 1.0, 1.0, -0.6667, -0.04167, 0.0, -0.25, -0.6667, 1.0, 0.1667, 0.0, 0.0, 0.2222, -1.0, -0.5, 0.0, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0,
0.0, 0.0, -1.0,
],
&[
0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.2222, -1.0, -0.5, 0.0, -0.25, -0.6667, 1.0, 0.1667, 1.0, 1.0, 1.0,
-0.6667, -0.04167,
],
),
(
5,
1,
1,
&[1.0, 2.0, 3.0, 2.0, 1.0],
&[
1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -6e-18, -6e-18, -6e-18, -6e-18, 0.0, 0.0, 3.6e-35, 3.6e-35, 3.6e-35, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0,
],
&[
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.6e-35, 3.6e-35, 3.6e-35, 0.0, -6e-18, -6e-18, -6e-18, -6e-18, 1.0, 1.0,
1.0, 1.0, 1.0,
],
),
(
6,
4,
6,
&[4.0, 3.0, 5.0, 100.0, 0.1, 1.0],
&[
1.0, 1.336e-06, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -3.001e-11, -3.252e-05, 0.01305, 0.0, 0.0, -0.00833, 8.929e-10, -6.712e-05,
6.687e-05, 0.0, 0.0, 0.0, -4.455e-06, -0.003355, 0.003345, 0.0, 0.0, 0.0, 4.455e-07, -0.03356, 0.03344, 0.0, 0.0, 0.0, 4.411e-10,
0.1011, 0.1008,
],
&[
0.0, 0.0, 0.0, -0.0004455, -0.3355, 0.3345, 0.0, 0.0, 0.0, 4.455e-08, -0.003356, 0.003344, 0.0, 1.0, 0.0, -3.001e-11, -3.252e-05,
0.01305, 1.0, 1.336e-06, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, -0.00833, 8.929e-10, -6.712e-05, 6.687e-05, 0.0, 0.0, 0.0, 4.411e-10, 0.1011,
0.1008,
],
),
(
5,
1,
5,
&[100.0, 0.1, 0.01, 1.0, 10.0],
&[
0.0001366, -6.829e-05, 0.0001252, 1.0, 1.95e-15, 1.0, 1.0, -2.776e-17, 3.601e-06, -6.073e-18, 0.2736, -0.1363, 0.2503, -3.322e-06,
-0.002, 0.006909, -0.003443, 0.006196, 0.01666, 1.0, 0.3899, -0.2033, -0.342, -0.001, 6e-15,
],
&[
0.01366, -0.006829, 0.01252, 100.0, 1.95e-13, 0.1, 0.1, -2.776e-18, 3.601e-07, -6.073e-19, 0.002736, -0.001363, 0.002503, -3.322e-08,
-2e-05, 0.006909, -0.003443, 0.006196, 0.01666, 1.0, 3.899, -2.033, -3.42, -0.01, 6e-14,
],
),
(
6,
2,
5,
&[3.0, 1.0, 1.0, 1.0, 1.0, 4.0],
&[
1.0, 1.0, 2.776e-16, -2.405e-17, 0.0, 1.0, 0.0, 0.75, 1.0, 0.0852, 0.0, -1.52e-17, 0.0, 0.75, -0.8093, 1.0, 0.0, -1.52e-17, 0.0,
0.75, -0.09533, -0.5426, 1.0, -1.52e-17, 0.0, 0.75, -0.09533, -0.5426, -1.0, -1.52e-17, 0.0, 0.0, 0.0, 0.0, 0.0, 4.559e-17,
],
&[
0.0, 0.75, -0.8093, 1.0, 0.0, -1.52e-17, 0.0, 0.75, 1.0, 0.0852, 0.0, -1.52e-17, 1.0, 1.0, 2.776e-16, -2.405e-17, 0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 4.559e-17, 0.0, 0.75, -0.09533, -0.5426, -1.0, -1.52e-17, 0.0, 0.75, -0.09533, -0.5426, 1.0, -1.52e-17,
],
),
(
7,
2,
5,
&[3.0, 0.001, 0.01, 10.0, 0.1, 1.0, 6.0],
&[
1.0, -0.01105, 0.03794, -0.09378, -0.03481, 0.4465, -0.03602, 0.0, -0.4556, -0.4545, 1.0, 0.4639, -0.6512, 0.4781, 0.0, -0.2734,
-0.7946, 0.6303, 1.0, -0.6279, 1.0, 0.0, 1.0, -6.939e-18, 0.04259, -0.6495, -0.5581, -0.6452, 0.0, -0.3904, -0.4029, -0.1685,
-0.9429, 1.0, -0.9371, 0.0, 0.0, 0.0, 0.0, 0.0, -0.2558, 0.0003308, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.001985,
],
&[
0.0, 0.0, 0.0, 0.0, 0.0, -0.2558, 0.0003308, 0.0, -0.0004556, -0.0004545, 0.001, 0.0004639, -0.0006512, 0.0004781, 1.0, -0.01105,
0.03794, -0.09378, -0.03481, 0.4465, -0.03602, 0.0, 10.0, -6.939e-17, 0.4259, -6.495, -5.581, -6.452, 0.0, -0.03904, -0.04029,
-0.01685, -0.09429, 0.1, -0.09371, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.001985, 0.0, -0.002734, -0.007946, 0.006303, 0.01, -0.006279,
0.01,
],
),
];
#[test]
fn dgebal_reproduces_the_reference_examples() {
for (k, &(n, a_in, ilo_in, ihi_in, a_ref_rows, scale_ref)) in DBAL_CASES.iter().enumerate() {
let case = k + 1;
let mut a = column_major(a_in, n);
let a_ref = column_major(a_ref_rows, n);
let (ilo, ihi, scale) = dgebal(&mut a, n);
assert_eq!((ilo + 1, ihi + 1), (ilo_in, ihi_in), "case {case} (n={n}): ILO/IHI disagree with dbal.in");
let norm = a_ref.iter().fold(0.0f64, |m, v| m.max(v.abs())).max(1.0);
for j in 0..n {
for i in 0..n {
let (got, want) = (a[i + j * n], a_ref[i + j * n]);
assert!(
(got - want).abs() <= 1e-13 * norm,
"case {case} (n={n}): balanced A({i},{j}) = {got}, the reference gives {want}"
);
}
}
for i in 0..n {
let (got, want) = (scale[i], scale_ref[i]);
assert!(
(got - want).abs() <= 1e-13 * want.abs().max(1.0),
"case {case} (n={n}): scale[{i}] = {got}, the reference gives {want}"
);
}
}
}
#[test]
fn zgebal_matches_dgebal_on_the_reference_examples() {
for (k, &(n, a_in, ilo_in, ihi_in, _, _)) in DBAL_CASES.iter().enumerate() {
let case = k + 1;
let a_real = column_major(a_in, n);
let mut a_complex: Vec<Complex64> = a_real.iter().map(|&v| Complex64::new(v, 0.0)).collect();
let mut a_work = a_real.clone();
let (ilo, ihi, scale) = dgebal(&mut a_work, n);
let (zilo, zihi, zscale) = crate::lapack::zeig::zgebal(&mut a_complex, n);
assert_eq!((zilo, zihi), (ilo, ihi), "case {case} (n={n}): zgebal disagrees with dgebal on ILO/IHI");
assert_eq!(
(zilo + 1, zihi + 1),
(ilo_in, ihi_in),
"case {case} (n={n}): both must agree with dbal.in"
);
let norm = a_work.iter().fold(0.0f64, |m, v| m.max(v.abs())).max(1.0);
for j in 0..n {
for i in 0..n {
let want = a_work[i + j * n];
let got = a_complex[i + j * n].re;
assert!(
(got - want).abs() <= 1e-13 * norm,
"case {case} (n={n}): zgebal gives {got} at ({i},{j}), dgebal gives {want}"
);
assert_eq!(a_complex[i + j * n].im, 0.0, "case {case}: a real matrix must stay real");
}
}
assert_eq!(zscale, scale, "case {case} (n={n}): scale vectors differ");
}
}
#[test]
fn dgebak_left_scales_rows_and_applies_the_permutation() {
let n = 6;
let (ilo, ihi) = (1usize, 4usize);
let scale = vec![3.0, 0.5, 2.0, 0.25, 8.0, 6.0];
let mut v = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
v[i + j * n] = (i as f64) + 0.5 * (j as f64) + 1.0;
}
}
let expected = |v: &[f64], scale: &[f64], ilo: usize, ihi: usize| -> Vec<f64> {
let mut w = v.to_vec();
if ilo != ihi {
for i in ilo..=ihi {
for j in 0..n {
w[i + j * n] /= scale[i];
}
}
}
for i in (0..ilo).rev() {
let k = scale[i] as usize - 1;
for j in 0..n {
w.swap(i + j * n, k + j * n);
}
}
for i in (ihi + 1)..n {
let k = scale[i] as usize - 1;
for j in 0..n {
w.swap(i + j * n, k + j * n);
}
}
w
};
for case_scale in [scale.clone(), {
let mut s = scale.clone();
s[2] = -2.0;
s
}] {
let want = expected(&v, &case_scale, ilo, ihi);
let mut got = v.clone();
dgebak_left(&mut got, n, ilo, ihi, &case_scale);
for idx in 0..n * n {
assert!(
(got[idx] - want[idx]).abs() <= 1e-13 * want[idx].abs().max(1.0),
"scale = {case_scale:?}, index {idx}: dgebak_left gives {}, the definition gives {}",
got[idx],
want[idx]
);
}
}
let tight = vec![4.0, 3.0, 1.0, 1.0, 1.0, 6.0];
let mut got = v.clone();
dgebak_left(&mut got, n, 2, 2, &tight);
let want = expected(&v, &tight, 2, 2);
for idx in 0..n * n {
assert!(
(got[idx] - want[idx]).abs() <= 1e-13 * want[idx].abs().max(1.0),
"ILO == IHI, index {idx}: {} != {}",
got[idx],
want[idx]
);
}
}
#[test]
fn dgebak_undoes_the_reference_balancing() {
for (k, &(n, ilo, ihi, scale, v_in, v_ref_rows)) in DBAK_CASES.iter().enumerate() {
let case = k + 1;
let mut v = column_major(v_in, n);
let v_ref = column_major(v_ref_rows, n);
dgebak_right(&mut v, n, ilo - 1, ihi - 1, scale);
let norm = v_ref.iter().fold(0.0f64, |m, x| m.max(x.abs())).max(1.0);
for j in 0..n {
for i in 0..n {
let (got, want) = (v[i + j * n], v_ref[i + j * n]);
assert!(
(got - want).abs() <= 1e-13 * norm,
"case {case} (n={n}): V({i},{j}) = {got}, dbak.in has {want}"
);
}
}
}
}
#[test]
fn dlartg_rotates_a_vector_onto_the_first_axis() {
let cases = [
(3.0, 4.0),
(-3.0, 4.0),
(3.0, -4.0),
(0.0, 5.0),
(5.0, 0.0),
(0.0, -5.0),
(1e300, 1e300), (1e-300, 1e-300), (1e300, 1.0),
(-2.5, 0.0),
];
for (f, g) in cases {
let (c, s, r) = dlartg(f, g);
let scale = f.abs().max(g.abs()).max(f64::MIN_POSITIVE);
assert!((c * c + s * s - 1.0).abs() <= 1e-14, "f = {f:e}, g = {g:e}: rotation not unit ({c},{s})");
let r1 = c * f + s * g;
let r2 = -s * f + c * g;
assert!(
(r1 - r).abs() <= 1e-13 * scale && r2.abs() <= 1e-13 * scale,
"f = {f:e}, g = {g:e}: rotation gives ({r1},{r2}) not ({r},0)"
);
assert!(
(r.abs() - f.hypot(g)).abs() <= 1e-12 * f.hypot(g) && r.is_finite(),
"f = {f:e}, g = {g:e}: |r| = {} is not the norm {}",
r.abs(),
f.hypot(g)
);
}
assert_eq!(dlartg(2.0, 0.0), (1.0, 0.0, 2.0));
assert_eq!(dlartg(0.0, -2.0), (0.0, -1.0, 2.0));
}
#[test]
fn dladiv_divides_without_overflowing() {
let cases = [
(1.0, 2.0, 3.0, 4.0),
(-1.5, 0.25, 2.0, -8.0),
(1.0, 0.0, 3.0, 4.0),
(0.0, 1.0, 0.0, 2.0),
(5.0, 0.0, 0.0, 2.0), ];
for (a, b, c, d) in cases {
let (p, q) = dladiv(a, b, c, d);
let den = c * c + d * d;
let (want_p, want_q) = ((a * c + b * d) / den, (b * c - a * d) / den);
let scale = want_p.abs().max(want_q.abs()).max(1.0);
assert!(
(p - want_p).abs() <= 1e-13 * scale && (q - want_q).abs() <= 1e-13 * scale,
"({a},{b})/({c},{d}): ({p},{q}) != ({want_p},{want_q})"
);
}
let (p, q) = dladiv(1e300, 0.0, 1e300, 1e300);
assert!(p.is_finite() && q.is_finite(), "({p},{q})");
assert!((p - 0.5).abs() < 1e-14 && (q + 0.5).abs() < 1e-14, "({p},{q})");
}
#[test]
fn dlaln2_solves_the_four_block_shapes() {
let r = dlaln2_real(1, 1, 0.0, 4.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0, 0.0);
assert_eq!(r.scale, 1.0);
assert!((r.x11 - 2.0 / 3.0).abs() < 1e-15, "x11 = {}", r.x11);
let (a11, b11, b12, wr, wi) = (4.0, 1.0, 2.0, 1.0, 0.5);
let r = dlaln2_real(1, 2, 0.0, a11, 0.0, 0.0, 0.0, b11, 0.0, b12, 0.0, wr, wi);
assert_eq!(r.scale, 1.0);
let den = (a11 - wr) * (a11 - wr) + wi * wi;
let want_re = (b11 * (a11 - wr) - b12 * wi) / den;
let want_im = (b12 * (a11 - wr) + b11 * wi) / den;
assert!((r.x11 - want_re).abs() < 1e-14, "x11 = {} want {want_re}", r.x11);
assert!((r.x12 - want_im).abs() < 1e-14, "x12 = {} want {want_im}", r.x12);
let (a11, a21, a12, a22) = (4.0, 2.0, 1.0, 3.0);
let (b11, b21, wr) = (1.0, -2.0, 0.5);
let r = dlaln2_real(2, 1, 0.0, a11, a21, a12, a22, b11, b21, 0.0, 0.0, wr, 0.0);
assert_eq!(r.scale, 1.0);
let det = (a11 - wr) * (a22 - wr) - a12 * a21;
let want_x11 = ((a22 - wr) * b11 - a12 * b21) / det;
let want_x21 = (-a21 * b11 + (a11 - wr) * b21) / det;
assert!((r.x11 - want_x11).abs() < 1e-14, "x11 = {}", r.x11);
assert!((r.x21 - want_x21).abs() < 1e-14, "x21 = {}", r.x21);
let (wr, wi) = (0.5, 1.5);
let (b11, b21, b12, b22) = (1.0, -1.0, 0.5, 2.0);
let r = dlaln2_real(2, 2, 0.0, a11, a21, a12, a22, b11, b21, b12, b22, wr, wi);
assert_eq!(r.scale, 1.0);
let m11 = Complex64::new(a11 - wr, -wi);
let m12 = Complex64::new(a12, 0.0);
let m21 = Complex64::new(a21, 0.0);
let m22 = Complex64::new(a22 - wr, -wi);
let rhs1 = Complex64::new(b11, b12);
let rhs2 = Complex64::new(b21, b22);
let det = m11 * m22 - m12 * m21;
let want1 = (rhs1 * m22 - m12 * rhs2) / det;
let want2 = (m11 * rhs2 - rhs1 * m21) / det;
let got1 = Complex64::new(r.x11, r.x12);
let got2 = Complex64::new(r.x21, r.x22);
assert!((got1 - want1).norm() < 1e-13, "x1 = {got1:?} want {want1:?}");
assert!((got2 - want2).norm() < 1e-13, "x2 = {got2:?} want {want2:?}");
}
#[test]
fn dlaln2_rescales_instead_of_overflowing() {
let (a11, b11) = (1e-300, 1e300);
let r = dlaln2_real(1, 1, 0.0, a11, 0.0, 0.0, 0.0, b11, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(r.scale < 1.0, "scale = {} should be below one", r.scale);
assert!(r.x11.is_finite(), "x11 = {} overflowed", r.x11);
let lhs = r.x11 * a11;
let rhs = r.scale * b11;
assert!(
(lhs - rhs).abs() <= 1e-13 * rhs.abs().max(f64::MIN_POSITIVE),
"({lhs:e}) != scale * b ({rhs:e})"
);
let smin = 1e-3;
let r = dlaln2_real(1, 1, smin, 1e-8, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(r.x11.abs() <= 1.0 / smin * 1.000001, "x11 = {} exceeds b/smin = {}", r.x11, 1.0 / smin);
assert!(r.x11.is_finite());
}
#[test]
fn dlanv2_handles_the_triangular_and_nearly_equal_blocks() {
let (mut a, mut b, mut c, mut d) = (2.0, 0.0, 3.0, 1.0);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
assert_eq!(c, 0.0, "the block must end up upper triangular");
assert_eq!((a, d), (1.0, 2.0), "the diagonal must be exchanged");
assert_eq!((cs, sn), (0.0, 1.0), "the rotation must be the exchange");
assert_eq!((e.rt1i, e.rt2i), (0.0, 0.0));
assert_eq!((e.rt1r, e.rt2r), (1.0, 2.0));
let (mut a, mut b, mut c, mut d) = (1.0, 2.0, 0.0, 3.0);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
assert_eq!((cs, sn), (1.0, 0.0));
assert_eq!((e.rt1r, e.rt2r), (1.0, 3.0));
let (mut a, mut b, mut c, mut d) = (1.0, 1e-9, 2e-9, 1.0);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
assert_eq!(e.rt1i, 0.0, "the eigenvalues are real");
assert_eq!(e.rt2i, 0.0);
assert!((cs * cs + sn * sn - 1.0).abs() < 1e-14);
let (mut a, mut b, mut c, mut d) = (1e-200, 1e-200, -1e-200, 1e-200);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
assert!((cs * cs + sn * sn - 1.0).abs() < 1e-14);
assert!(
e.rt1r.is_finite() && e.rt2r.is_finite() && e.rt1i.is_finite(),
"eigenvalues must stay finite: {e:?}"
);
}
#[test]
fn dgeev_returns_normalized_eigenvectors() {
for n in [2usize, 3, 5, 8] {
let orig = sample(n);
let mut a = orig.clone();
let (mut vr, mut vi) = (vec![0.0; n * n], vec![0.0; n * n]);
let (wr, wi) = dgeev(&mut vr, &mut vi, &mut a, n).expect("dgeev");
let mut j = 0;
while j < n {
if wi[j] == 0.0 {
let norm = (0..n).map(|i| vr[i + j * n] * vr[i + j * n]).sum::<f64>().sqrt();
assert!((norm - 1.0).abs() < 1e-13, "n={n}, j={j}: norm = {norm}");
for i in 0..n {
assert_eq!(vi[i + j * n], 0.0, "n={n}: real column {j} has an imaginary part");
}
j += 1;
} else {
let norm = (0..n)
.map(|i| vr[i + j * n] * vr[i + j * n] + vi[i + j * n] * vi[i + j * n])
.sum::<f64>()
.sqrt();
assert!((norm - 1.0).abs() < 1e-13, "n={n}, pair {j}: norm = {norm}");
let (mut imax, mut best) = (0, 0.0);
for i in 0..n {
let m = vr[i + j * n].hypot(vi[i + j * n]);
if m > best {
best = m;
imax = i;
}
}
assert!(
vi[imax + j * n].abs() < 1e-12 * best.max(1.0),
"n={n}, pair {j}: the largest component (row {imax}) is not real \
({} + {}i)",
vr[imax + j * n],
vi[imax + j * n]
);
assert_eq!((wr.len(), wi.len()), (n, n));
j += 2;
}
}
}
}
#[test]
fn dlanv2_real_eigenvalues() {
let (mut a, mut b, mut c, mut d) = (1.0, 2.0, 3.0, 4.0);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
assert_eq!(e.rt1i, 0.0);
assert_eq!(e.rt2i, 0.0);
assert!(c.abs() < 1e-14, "the block must be made upper triangular, c = {c}");
let disc = 33.0f64.sqrt();
for want in [(5.0 + disc) / 2.0, (5.0 - disc) / 2.0] {
assert!(
[e.rt1r, e.rt2r].iter().any(|g| (g - want).abs() < 1e-13),
"missing eigenvalue {want} in {:?}",
[e.rt1r, e.rt2r]
);
}
}
#[test]
fn dlanv2_complex_pair() {
let (mut a, mut b, mut c, mut d) = (0.0, 1.0, -1.0, 0.0);
let (mut cs, mut sn) = (0.0, 0.0);
let e = dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
assert!(e.rt1i > 0.0, "expected a complex pair with a positive rt1i, got {e:?}");
assert!((e.rt1i + e.rt2i).abs() < 1e-14, "the pair must be conjugate");
assert!(e.rt1r.abs() < 1e-14);
assert!((e.rt1i - 1.0).abs() < 1e-14, "|imag| should be 1, got {}", e.rt1i);
assert!((a - d).abs() < 1e-14, "the diagonal must be equal: {a} vs {d}");
assert!(b * c < 0.0, "B*C must be negative, got {} * {}", b, c);
}
#[test]
fn dgehrd_matches_the_3x3_reference() {
let a0 = [4.0, 2.0, -2.0, -3.0, 4.5, 0.0, -1.0, -5.0, 5.0];
let n = 3usize;
let (beta, tau, v) = dlarfg(a0[1], &[a0[2]]);
assert!((beta + 2.8284271247461903).abs() < 1e-14, "beta = {beta}");
let mut hr = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
hr[i + j * n] = if i == 0 && j == 0 {
1.0
} else if i > 0 && j > 0 {
(if i == j { 1.0 } else { 0.0 }) - tau * v[i - 1] * v[j - 1]
} else {
0.0
};
}
}
let mul = |x: &[f64], y: &[f64]| {
let mut c = vec![0.0; n * n];
for j in 0..n {
for i in 0..n {
let mut s = 0.0;
for l in 0..n {
s += x[i + l * n] * y[l + j * n];
}
c[i + j * n] = s;
}
}
c
};
let want = mul(&hr, &mul(&a0, &hr));
let mut h = a0;
dgehrd(&mut h, n, None);
for idx in 0..n * n {
assert!(
(h[idx] - want[idx]).abs() < 1e-13,
"H[{idx}] = {} but Hr A Hr gives {}",
h[idx],
want[idx]
);
}
assert!((h[1] - -2.8284271247461903).abs() < 1e-13, "H(1,0) = {}", h[1]);
assert_eq!(h[2], 0.0, "H(2,0) must be exactly zero");
assert!((h[5] - -2.25).abs() < 1e-13, "H(2,1) = {}", h[5]);
}
#[test]
fn dhseqr_with_schur_vectors_gives_the_schur_decomposition() {
for n in [1usize, 2, 3, 5, 8, 12] {
let orig = sample(n);
let mut h = orig.clone();
let mut z = vec![0.0; n * n];
dgehrd(&mut h, n, Some(&mut z));
let mut wr = vec![0.0; n];
let mut wi = vec![0.0; n];
dhseqr_with_schur_vectors(&mut h, &mut wr, &mut wi, &mut z, n).unwrap();
for j in 0..n {
for k in 0..n {
let s: f64 = (0..n).map(|i| z[i + j * n] * z[i + k * n]).sum();
let want = if j == k { 1.0 } else { 0.0 };
assert!((s - want).abs() < 1e-12, "n={n}: Z^T Z ({j},{k}) = {s}");
}
}
let az = mat_mul(&orig, &z, n);
let zt = mat_mul(&z, &h, n);
let scale = norm_max(&orig).max(1.0);
for idx in 0..n * n {
assert!(
(az[idx] - zt[idx]).abs() / scale < 1e-12,
"n={n}: A Z and Z T differ at {idx}: {} vs {}",
az[idx],
zt[idx]
);
}
for j in 0..n {
for i in (j + 2)..n {
assert!(h[i + j * n].abs() < 1e-13, "n={n}: T({i},{j}) is below the subdiagonal");
}
}
let mut h2 = orig.clone();
dgehrd(&mut h2, n, None);
let mut wr2 = vec![0.0; n];
let mut wi2 = vec![0.0; n];
dhseqr(&mut h2, &mut wr2, &mut wi2, n).unwrap();
for i in 0..n {
assert!((wr[i] - wr2[i]).abs() < 1e-13 && (wi[i] - wi2[i]).abs() < 1e-13);
}
}
}
#[test]
fn dgeev_eigenvectors_satisfy_their_defining_equation() {
for n in [1usize, 2, 3, 5, 8, 12] {
let data = sample(n);
let a = data.clone();
let mut av = data.clone();
let mut v_real = vec![0.0; n * n];
let mut v_imag = vec![0.0; n * n];
let (wr_vec, wi_vec) = dgeev(&mut v_real, &mut v_imag, &mut av, n).unwrap();
let (wr, wi) = (&wr_vec, &wi_vec);
let scale = a.iter().fold(0.0f64, |m, x| m.max(x.abs())).max(1.0);
let mut j = 0usize;
while j < n {
if wi[j] != 0.0 {
let (lr, li) = (wr[j], wi[j]);
let mut worst = 0.0f64;
for i in 0..n {
let mut sr = 0.0;
let mut si = 0.0;
for k in 0..n {
let ar = a[i + k * n];
let vr = v_real[k + j * n];
let vi = v_imag[k + j * n];
sr += ar * vr;
si += ar * vi;
}
let dr = sr - (lr * v_real[i + j * n] - li * v_imag[i + j * n]);
let di = si - (lr * v_imag[i + j * n] + li * v_real[i + j * n]);
worst = worst.max(dr.abs().max(di.abs()));
}
assert!(
worst / scale < 1e-10,
"n={n}: the complex eigenvector for lambda = {lr}+{li}i has residual {worst} (scale {scale})"
);
j += 2;
} else {
let l = wr[j];
let mut worst = 0.0f64;
for i in 0..n {
let mut s = 0.0;
for k in 0..n {
s += a[i + k * n] * v_real[k + j * n];
}
worst = worst.max((s - l * v_real[i + j * n]).abs());
}
assert!(
worst / scale < 1e-10,
"n={n}: the real eigenvector for lambda = {l} has residual {worst} (scale {scale})"
);
j += 1;
}
}
}
}
#[test]
fn dgeev_residuals_on_a_battery_of_matrices() {
let mut state: u64 = 0x243f_6a88_85a3_08d3;
let mut next = move || {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 11) as f64) / ((1u64 << 53) as f64) - 0.5
};
let mut cases: Vec<(usize, Vec<f64>)> = Vec::new();
let mut jordan = vec![0.0; 16];
for i in 0..4 {
jordan[i + i * 4] = 2.0;
if i < 3 {
jordan[i + (i + 1) * 4] = 1.0;
}
}
cases.push((4, jordan));
cases.push((2, vec![1.0, -2.0, 2.0, 1.0]));
let mut blockrot = vec![0.0; 36];
for b in 0..3usize {
let o = 2 * b;
blockrot[o + o * 6] = 1.0 + b as f64;
blockrot[(o + 1) + o * 6] = -2.0;
blockrot[o + (o + 1) * 6] = 2.0;
blockrot[(o + 1) + (o + 1) * 6] = 1.0 + b as f64;
}
cases.push((6, blockrot));
for n in [3usize, 5, 9, 16] {
for _ in 0..4 {
let mut a = vec![0.0; n * n];
for x in a.iter_mut() {
*x = next() * 20.0;
}
cases.push((n, a));
}
}
for (n, data) in cases {
let a = data.clone();
let mut av = vec![0.0; n * n];
let mut v_real = vec![0.0; n * n];
let mut v_imag = vec![0.0; n * n];
let (wr, wi) = dgeev(&mut av, &mut v_real, &mut v_imag, n).unwrap();
let scale = a.iter().fold(0.0f64, |m, x| m.max(x.abs())).max(1.0);
let mut j = 0usize;
while j < n {
let (lr, li) = (wr[j], wi[j]);
let complex = li != 0.0;
let mut worst = 0.0f64;
for i in 0..n {
let mut sr = 0.0;
let mut si = 0.0;
for k in 0..n {
sr += a[i + k * n] * v_real[k + j * n];
si += a[i + k * n] * v_imag[k + j * n];
}
let dr = sr - (lr * v_real[i + j * n] - li * v_imag[i + j * n]);
let di = si - (lr * v_imag[i + j * n] + li * v_real[i + j * n]);
worst = worst.max(dr.abs().max(di.abs()));
}
assert!(worst / scale < 1e-10, "n={n}: residual {worst} (scale {scale}) for lambda = {lr}{li:+}i");
j += if complex { 2 } else { 1 };
}
}
}
#[test]
fn dgeev_lr_left_eigenvectors_satisfy_their_defining_equation() {
let mut state: u64 = 0x9e37_79b9_7f4a_7c15;
let mut next = move || {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 11) as f64) / ((1u64 << 53) as f64) - 0.5
};
let mut cases: Vec<(usize, Vec<f64>)> = Vec::new();
for n in [1usize, 2, 3, 5, 8] {
cases.push((n, sample(n)));
}
cases.push((2, vec![1.0, -2.0, 2.0, 1.0]));
let mut blockrot = vec![0.0; 36];
for b in 0..3usize {
let o = 2 * b;
blockrot[o + o * 6] = 1.0 + b as f64;
blockrot[(o + 1) + o * 6] = -2.0;
blockrot[o + (o + 1) * 6] = 2.0;
blockrot[(o + 1) + (o + 1) * 6] = 1.0 + b as f64;
}
cases.push((6, blockrot));
for n in [3usize, 7] {
for _ in 0..4 {
let mut a = vec![0.0; n * n];
for x in a.iter_mut() {
*x = next() * 20.0;
}
cases.push((n, a));
}
}
for (n, data) in cases {
let a = data.clone();
let mut av = data.clone();
let mut u_real = vec![0.0; n * n];
let mut u_imag = vec![0.0; n * n];
let mut v_real = vec![0.0; n * n];
let mut v_imag = vec![0.0; n * n];
let (wr, wi) = dgeev_lr(&mut u_real, &mut u_imag, &mut v_real, &mut v_imag, &mut av, n).unwrap();
let scale = a.iter().fold(0.0f64, |m, x| m.max(x.abs())).max(1.0);
let mut j = 0usize;
while j < n {
let (lr, li) = (wr[j], wi[j]);
let complex = li != 0.0;
let mut worst = 0.0f64;
for i in 0..n {
let mut sr = 0.0;
let mut si = 0.0;
for k in 0..n {
sr += a[k + i * n] * u_real[k + j * n];
si += a[k + i * n] * u_imag[k + j * n];
}
let ur = u_real[i + j * n];
let ui = u_imag[i + j * n];
let dr = sr - (lr * ur + li * ui);
let di = si - (lr * ui - li * ur);
worst = worst.max(dr.abs().max(di.abs()));
}
assert!(
worst / scale < 1e-12,
"n={n}: left residual {worst} (scale {scale}) for lambda = {lr}{li:+}i"
);
j += if complex { 2 } else { 1 };
}
let mut vr = vec![0.0; n * n];
let mut vi = vec![0.0; n * n];
let mut aw = data.clone();
let (wr2, wi2) = dgeev(&mut vr, &mut vi, &mut aw, n).unwrap();
for i in 0..n {
assert!(wr[i] == wr2[i] && wi[i] == wi2[i], "n={n}: eigenvalue {i} differs");
for r in 0..n {
assert!(
(v_real[r + i * n] - vr[r + i * n]).abs() < 1e-13 && (v_imag[r + i * n] - vi[r + i * n]).abs() < 1e-13,
"n={n}: right eigenvector ({r},{i}) differs"
);
}
}
}
}
#[test]
fn non_convergence_is_reported_rather_than_returned_as_a_result() {
for n in [4usize, 8] {
let mut wr = vec![0.0; n];
let mut wi = vec![0.0; n];
let mut t = vec![f64::NAN; n * n];
assert_eq!(
dhseqr(&mut t, &mut wr, &mut wi, n),
Err(LapackError::Convergence),
"dhseqr n={n} must report non-convergence"
);
let mut z = vec![0.0; n * n];
for i in 0..n {
z[i + i * n] = 1.0;
}
let mut t2 = vec![f64::NAN; n * n];
assert_eq!(
dhseqr_with_schur_vectors(&mut t2, &mut wr, &mut wi, &mut z, n),
Err(LapackError::Convergence),
"dhseqr_with_schur_vectors n={n} must report non-convergence"
);
let mut v_real = vec![0.0; n * n];
let mut v_imag = vec![0.0; n * n];
let mut a = vec![f64::NAN; n * n];
assert_eq!(
dgeev(&mut v_real, &mut v_imag, &mut a, n).map(|_| ()),
Err(LapackError::Convergence),
"dgeev n={n} must report non-convergence"
);
let mut u_real = vec![0.0; n * n];
let mut u_imag = vec![0.0; n * n];
let mut v_real2 = vec![0.0; n * n];
let mut v_imag2 = vec![0.0; n * n];
let mut a2 = vec![f64::NAN; n * n];
assert_eq!(
dgeev_lr(&mut u_real, &mut u_imag, &mut v_real2, &mut v_imag2, &mut a2, n).map(|_| ()),
Err(LapackError::Convergence),
"dgeev_lr n={n} must report non-convergence"
);
}
}
#[test]
fn dgeev_handles_matrices_at_the_edges_of_the_range() {
let n = 4usize;
let eps = f64::EPSILON;
let cases = [
(1e300f64, 1e300f64),
(1e-300, 1e-300),
(1e250, 1e250),
(1e-250, 1e-250),
(1.0, 1.0),
(1.0, 1e200),
(1e-200, 1e200),
(1e-100, 1e150),
];
for (d, o) in cases {
let mut a0 = vec![0.0; n * n];
for i in 0..n {
a0[i + i * n] = d * (i as f64 + 1.0);
for j in (i + 1)..n {
a0[i + j * n] = o * 0.3 / (j - i) as f64;
}
}
let mut a = a0.clone();
let mut u_real = vec![0.0; n * n];
let mut u_imag = vec![0.0; n * n];
let mut v_real = vec![0.0; n * n];
let mut v_imag = vec![0.0; n * n];
let (wr, wi) = dgeev_lr(&mut u_real, &mut u_imag, &mut v_real, &mut v_imag, &mut a, n).expect("dgeev_lr");
let anorm = a0.iter().fold(0.0f64, |m, x| m.max(x.abs()));
let bound = n as f64 * anorm * eps;
for j in 0..n {
assert!(wr[j].is_finite() && wi[j].is_finite(), "d = {d}, o = {o}: eigenvalue {j} is not finite");
}
let mut j = 0usize;
while j < n {
let (lr, li) = (wr[j], wi[j]);
let complex = li != 0.0;
for (v_re, v_im, left) in [(&v_real, &v_imag, false), (&u_real, &u_imag, true)] {
let mut worst = 0.0f64;
for i in 0..n {
let mut row = 0.0;
let mut row_i = 0.0;
for k in 0..n {
let (a_ik, a_ki) = if left { (a0[k + i * n], 0.0) } else { (a0[i + k * n], 0.0) };
row += a_ik * v_re[k + j * n] - a_ki * v_im[k + j * n];
row_i += a_ik * v_im[k + j * n] + a_ki * v_re[k + j * n];
}
let dr = row - (lr * v_re[i + j * n] - li * v_im[i + j * n]);
let di = row_i - (lr * v_im[i + j * n] + li * v_re[i + j * n]);
worst = worst.max(dr.abs().max(di.abs()));
}
let ratio = worst / bound;
assert!(ratio <= 20.0, "d = {d}, o = {o}, j = {j}, left = {left}: ratio = {ratio:e}");
}
j += if complex { 2 } else { 1 };
}
}
}
}