use super::eig::dlanv2;
pub fn get33() -> (f64, usize, usize) {
let eps = f64::EPSILON;
let smlnum = f64::MIN_POSITIVE / eps;
let bignum = 1.0 / smlnum;
let val = [1.0, 1.0 + 2.0 * eps, 2.0, 2.0 - 4.0 * eps];
let vm = [smlnum, 1.0, bignum];
let mut knt = 0usize;
let mut lmax = 0usize;
let mut rmax = 0.0f64;
for i1 in 0..4 {
for i2 in 0..4 {
for i3 in 0..4 {
for i4 in 0..4 {
for im1 in 0..3 {
for im2 in 0..3 {
for im3 in 0..3 {
for im4 in 0..3 {
let t = [[val[i1] * vm[im1], val[i2] * vm[im2]], [-val[i3] * vm[im3], val[i4] * vm[im4]]];
let tnrm = t[0][0].abs().max(t[0][1].abs()).max(t[1][0].abs()).max(t[1][1].abs());
let (mut a, mut b, mut c, mut d) = (t[0][0], t[0][1], t[1][0], t[1][1]);
let (mut cs, mut sn) = (0.0f64, 0.0f64);
dlanv2(&mut a, &mut b, &mut c, &mut d, &mut cs, &mut sn);
let (mut q11, mut q12) = (1.0f64, 0.0f64);
let (mut q21, mut q22) = (0.0f64, 1.0f64);
let r = q11 * cs + q12 * sn;
q12 = -q11 * sn + q12 * cs;
q11 = r;
let r = q21 * cs + q22 * sn;
q22 = -q21 * sn + q22 * cs;
q21 = r;
let q = [[q11, q12], [q21, q22]];
let mut res = 0.0f64;
res += (q11 * q11 + q12 * q12 - 1.0).abs() / eps;
res += (q22 * q22 + q21 * q21 - 1.0).abs() / eps;
res += (q11 * q21 + q12 * q22).abs() / eps;
let mut t2 = [[0.0f64; 2]; 2];
for (j1, row) in t2.iter_mut().enumerate() {
for (j2, v) in row.iter_mut().enumerate() {
let mut sum = 0.0;
for k in 0..2 {
sum += t[j1][k] * q[k][j2];
}
*v = sum;
}
}
let tnew = [[a, b], [c, d]];
for (j1, row) in tnew.iter().enumerate() {
for (j2, tval) in row.iter().enumerate() {
let mut sum = *tval;
for k in 0..2 {
sum -= q[k][j1] * t2[k][j2];
}
res += sum.abs() / eps / tnrm;
}
}
let sign = |x: f64| if x < 0.0 { -1.0 } else { 1.0 };
if c != 0.0 && (a != d || sign(b) * sign(c) > 0.0) {
res += 1.0 / eps;
}
knt += 1;
if res > rmax {
lmax = knt;
rmax = res;
}
}
}
}
}
}
}
}
}
(rmax, lmax, knt)
}
pub struct Case {
pub ltrans: bool,
pub na: usize,
pub nw: usize,
pub smin: f64,
pub ca: f64,
pub a11: f64,
pub a21: f64,
pub a12: f64,
pub a22: f64,
pub d1: f64,
pub d2: f64,
pub b11: f64,
pub b21: f64,
pub b12: f64,
pub b22: f64,
pub wr: f64,
pub wi: f64,
}
pub fn get31_info(c: &Case) -> i32 {
const IPIVOT: [[usize; 4]; 4] = [[1, 2, 3, 4], [2, 1, 4, 3], [3, 4, 1, 2], [4, 3, 2, 1]];
let smlnum = 2.0 * f64::MIN_POSITIVE;
let smini = c.smin.max(smlnum);
if c.na == 1 {
let csr = c.ca * c.a11 - c.wr * c.d1;
let cnorm = if c.nw == 1 { csr.abs() } else { csr.abs() + (c.wi * c.d1).abs() };
return if cnorm < smini { 1 } else { 0 };
}
let crv = if c.ltrans {
[c.ca * c.a11 - c.wr * c.d1, c.ca * c.a12, c.ca * c.a21, c.ca * c.a22 - c.wr * c.d2]
} else {
[c.ca * c.a11 - c.wr * c.d1, c.ca * c.a21, c.ca * c.a12, c.ca * c.a22 - c.wr * c.d2]
};
if c.nw == 1 {
let mut cmax = 0.0;
let mut icmax = 0usize;
for (j, v) in crv.iter().enumerate() {
if v.abs() > cmax {
cmax = v.abs();
icmax = j;
}
}
if cmax < smini {
return 1;
}
let ur11r = 1.0 / crv[icmax];
let lr21 = ur11r * crv[IPIVOT[icmax][1] - 1];
let ur22 = crv[IPIVOT[icmax][3] - 1] - crv[IPIVOT[icmax][2] - 1] * lr21;
return if ur22.abs() < smini { 1 } else { 0 };
}
let civ = [-c.wi * c.d1, 0.0, 0.0, -c.wi * c.d2];
let mut cmax = 0.0;
let mut icmax = 0usize;
for (j, v) in crv.iter().enumerate() {
let m = v.abs() + civ[j].abs();
if m > cmax {
cmax = m;
icmax = j;
}
}
if cmax < smini {
return 1;
}
let ur11 = crv[icmax];
let ui11 = civ[icmax];
let cr21 = crv[IPIVOT[icmax][1] - 1];
let ci21 = civ[IPIVOT[icmax][1] - 1];
let ur12 = crv[IPIVOT[icmax][2] - 1];
let ui12 = civ[IPIVOT[icmax][2] - 1];
let cr22 = crv[IPIVOT[icmax][3] - 1];
let ci22 = civ[IPIVOT[icmax][3] - 1];
let u22abs = if icmax == 0 || icmax == 3 {
let (ur11r, ui11r) = if ur11.abs() > ui11.abs() {
let temp = ui11 / ur11;
let r = 1.0 / (ur11 * (1.0 + temp * temp));
(r, -temp * r)
} else {
let temp = ur11 / ui11;
let i = -1.0 / (ui11 * (1.0 + temp * temp));
(-temp * i, i)
};
let lr21 = cr21 * ur11r;
let li21 = cr21 * ui11r;
(cr22 - ur12 * lr21).abs() + (ci22 - ur12 * li21).abs()
} else {
let ur11r = 1.0 / ur11;
let lr21 = cr21 * ur11r;
let li21 = ci21 * ur11r;
(cr22 - ur12 * lr21 + ui12 * li21).abs() + (-ur12 * li21 - ui12 * lr21).abs()
};
if u22abs < smini { 1 } else { 0 }
}
fn scored(res: f64, zero: bool, scale: f64, xnorm: f64, xnorm_expected: f64, info: i32) -> f64 {
let eps = f64::EPSILON;
let smlnum = f64::MIN_POSITIVE / eps;
let mut r = if zero { 0.0 } else { res };
if scale > 1.0 {
r += 1.0 / eps;
}
r += (xnorm - xnorm_expected).abs() / smlnum.max(xnorm) / eps;
if info != 0 && info != 1 {
r += 1.0 / eps;
}
r
}
#[derive(Default)]
struct Tally {
knt: usize,
lmax: usize,
rmax: f64,
ninfo_neg: usize,
ninfo_pos: usize,
}
impl Tally {
fn add(&mut self, res: f64, info: i32) {
if info < 0 {
self.ninfo_neg += 1;
}
if info > 0 {
self.ninfo_pos += 1;
}
self.knt += 1;
if res > self.rmax {
self.lmax = self.knt;
self.rmax = res;
}
}
}
pub fn get31<F>(solve: F) -> (f64, usize, usize, usize, usize)
where
F: Fn(&Case) -> ([f64; 4], f64, f64, i32),
{
get31_where(|_| true, solve)
}
fn get31_where<K, F>(keep: K, solve: F) -> (f64, usize, usize, usize, usize)
where
K: Fn(&Case) -> bool,
F: Fn(&Case) -> ([f64; 4], f64, f64, i32),
{
let eps = f64::EPSILON;
let unfl = f64::MIN_POSITIVE;
let smlnum = f64::MIN_POSITIVE / eps;
let bignum = 1.0 / smlnum;
let vsm = [smlnum, eps, 1.0 / (10.0 * 10.0), 1.0 / eps];
let vab = [smlnum.sqrt(), 1.0, bignum.sqrt()];
let vwr = [0.0, 0.5, 2.0, 1.0];
let vwi = [smlnum, eps, 1.0, 2.0];
let vdd = [smlnum.sqrt(), 1.0, 2.0, bignum.sqrt()];
let vca = [0.0, smlnum.sqrt(), eps, 0.5, 1.0];
let mut tally = Tally::default();
for &d1 in &vdd {
for &d2 in &vdd {
for &ca in &vca {
for <rans in &[false, true] {
for &smin in &vsm {
let special = d1 == 1.0 && d2 == 1.0 && ca == 1.0;
for &av in &vab {
for &bv in &vab {
for &wrv in &vwr {
let wr = if special { wrv * av } else { wrv };
let case = Case {
ltrans,
na: 1,
nw: 1,
smin,
ca,
a11: av,
a21: 0.0,
a12: 0.0,
a22: 0.0,
d1,
d2,
b11: bv,
b21: 0.0,
b12: 0.0,
b22: 0.0,
wr,
wi: 0.0,
};
if !keep(&case) {
continue;
}
let (x, scale, xnorm, info) = solve(&case);
let x11 = x[0];
let csr = ca * av - wr * d1;
let mut res = (csr * x11 - scale * bv).abs();
let den = if info == 0 {
(eps * (csr * x11).abs()).max(smlnum)
} else {
(smin * x11.abs()).max(smlnum)
};
res /= den;
let zero = x11.abs() < unfl && bv.abs() <= smlnum * csr.abs();
tally.add(scored(res, zero, scale, xnorm, x11.abs(), info), info);
}
}
}
for &av in &vab {
for &bv in &vab {
let b1 = bv;
let b2 = -0.5 * bv;
for &wrv in &vwr {
let wr = if special { wrv * av } else { wrv };
for &wiv in &vwi {
let wi = if special { wiv * av } else { wiv };
let case = Case {
ltrans,
na: 1,
nw: 2,
smin,
ca,
a11: av,
a21: 0.0,
a12: 0.0,
a22: 0.0,
d1,
d2,
b11: b1,
b21: 0.0,
b12: b2,
b22: 0.0,
wr,
wi,
};
if !keep(&case) {
continue;
}
let (x, scale, xnorm, info) = solve(&case);
let (x11, x12) = (x[0], x[2]);
let csr = ca * av - wr * d1;
let csi = wi * d1;
let mut res = (csr * x11 + csi * x12 - scale * b1).abs();
res += (csr * x12 - csi * x11 - scale * b2).abs();
let den = if info == 0 {
(eps * csr.abs().max(csi.abs()) * (x11.abs() + x12.abs())).max(smlnum)
} else {
(smin * (x11.abs() + x12.abs())).max(smlnum)
};
res /= den;
let zero = x11.abs() < unfl && x12.abs() < unfl && b1.abs() <= smlnum * csr.abs();
tally.add(scored(res, zero, scale, xnorm, x11.abs() + x12.abs(), info), info);
}
}
}
}
for &av in &vab {
let mut a = [av, -7.0 * av, -3.0 * av, 21.0 * av];
for &bv in &vab {
let b = [bv, -2.0 * bv, 0.0, 0.0];
for &wrv in &vwr {
let wr = if special { wrv * a[0] } else { wrv };
let case = Case {
ltrans,
na: 2,
nw: 1,
smin,
ca,
a11: a[0],
a21: a[1],
a12: a[2],
a22: a[3],
d1,
d2,
b11: b[0],
b21: b[1],
b12: b[2],
b22: b[3],
wr,
wi: 0.0,
};
let out = if keep(&case) { Some(solve(&case)) } else { None };
if ltrans {
a.swap(1, 2);
}
if let Some((x, scale, xnorm, info)) = out {
let (x11, x21) = (x[0], x[1]);
let r1 = ca * a[0] - wr * d1;
let r2 = ca * a[3] - wr * d2;
let c1 = r1.abs() + (ca * a[2]).abs();
let c2 = (ca * a[1]).abs() + r2.abs();
let mut res = (r1 * x11 + ca * a[2] * x21 - scale * b[0]).abs();
res += (ca * a[1] * x11 + r2 * x21 - scale * b[1]).abs();
let xmax = x11.abs().max(x21.abs());
let den = if info == 0 {
(eps * c1.max(c2) * xmax).max(smlnum)
} else {
(eps * (smin / eps).max(c1.max(c2)) * xmax).max(smlnum)
};
res /= den;
let zero = x11.abs() < unfl && x21.abs() < unfl && b[0].abs() + b[1].abs() <= smlnum * (c1 + c2);
tally.add(scored(res, zero, scale, xnorm, xmax, info), info);
}
}
}
}
for &av in &vab {
let mut a = [2.0 * av, -7.0 * av, -3.0 * av, 21.0 * av];
for &bv in &vab {
let b = [bv, -2.0 * bv, 4.0 * bv, -7.0 * bv];
for &wrv in &vwr {
let wr = if special { wrv * a[0] } else { wrv };
for &wiv in &vwi {
let wi = if special { wiv * a[0] } else { wiv };
let case = Case {
ltrans,
na: 2,
nw: 2,
smin,
ca,
a11: a[0],
a21: a[1],
a12: a[2],
a22: a[3],
d1,
d2,
b11: b[0],
b21: b[1],
b12: b[2],
b22: b[3],
wr,
wi,
};
let out = if keep(&case) { Some(solve(&case)) } else { None };
if ltrans {
a.swap(1, 2);
}
if let Some((x, scale, xnorm, info)) = out {
let (x11, x21, x12, x22) = (x[0], x[1], x[2], x[3]);
let r1 = ca * a[0] - wr * d1;
let r2 = ca * a[3] - wr * d2;
let s1 = wi * d1;
let s2 = wi * d2;
let c1 = r1.abs() + (ca * a[2]).abs() + s1.abs();
let c2 = (ca * a[1]).abs() + r2.abs() + s2.abs();
let mut res = (r1 * x11 + ca * a[2] * x21 + s1 * x12 - scale * b[0]).abs();
res += (r1 * x12 + ca * a[2] * x22 - s1 * x11 - scale * b[2]).abs();
res += (ca * a[1] * x11 + r2 * x21 + s2 * x22 - scale * b[1]).abs();
res += (ca * a[1] * x12 + r2 * x22 - s2 * x21 - scale * b[3]).abs();
let xmax = (x11.abs() + x21.abs()).max(x12.abs() + x22.abs());
let den = if info == 0 {
(eps * c1.max(c2) * xmax).max(smlnum)
} else {
(eps * (smin / eps).max(c1.max(c2)) * xmax).max(smlnum)
};
res /= den;
let zero = x11.abs() < unfl
&& x21.abs() < unfl
&& x12.abs() < unfl
&& x22.abs() < unfl
&& b[0].abs() + b[1].abs() <= smlnum * (c1 + c2);
let xnorm_expected = (x11.abs() + x12.abs()).max(x21.abs() + x22.abs());
tally.add(scored(res, zero, scale, xnorm, xnorm_expected, info), info);
}
}
}
}
}
}
}
}
}
}
(tally.rmax, tally.lmax, tally.ninfo_neg, tally.ninfo_pos, tally.knt)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dlanv2_matches_the_reference_examples() {
let (rmax, lmax, knt) = get33();
println!("dlanv2 over the reference's example grid: {knt} cases, rmax = {rmax:e} at case {lmax}");
assert_eq!(knt, 20736, "the grid must have 4^4 * 3^4 cases");
assert!(
rmax <= 20.0,
"dlanv2 on the reference's examples: rmax = {rmax:e} at case {lmax}, where LAPACK's own dlanv2 reports 6.75"
);
assert!(lmax > 0 || rmax == 0.0, "lmax must name a case when rmax is nonzero");
}
#[test]
fn dlaln2_matches_the_reference_examples() {
use crate::lapack::auxiliary::dlaln2;
let (rmax, lmax, ninfo_neg, ninfo_pos, knt) = get31(|c| {
let r = dlaln2(
c.ltrans, c.na, c.nw, c.smin, c.ca, c.a11, c.a21, c.a12, c.a22, c.d1, c.d2, c.b11, c.b21, c.b12, c.b22, c.wr, c.wi,
);
([r.x11, r.x21, r.x12, r.x22], r.scale, r.xnorm, get31_info(c))
});
println!("dlaln2 over the reference's example grid: {knt} cases, ninfo = ({ninfo_neg}, {ninfo_pos}), rmax = {rmax:e} at case {lmax}");
assert_eq!(knt, 230400, "the grid must have 640 * 360 cases");
assert_eq!(ninfo_neg, 0, "the reference does not check its arguments");
assert_eq!(ninfo_pos, 74796, "the perturbed cases must be the reference's");
assert!(
rmax <= 20.0,
"dlaln2 on the reference's examples: rmax = {rmax:e} at case {lmax}, where LAPACK's own dlaln2 reports 1.7727"
);
assert_eq!(lmax, 249, "the worst case must be the reference's");
}
}