#![allow(clippy::many_single_char_names, clippy::float_cmp)]
use alloc::{vec, vec::Vec};
use libm::{copysign, fabs, sqrt};
struct M<'a> {
a: &'a mut [f64],
n: usize,
}
impl M<'_> {
fn g(&self, i: usize, j: usize) -> f64 {
self.a[(i - 1) * self.n + (j - 1)]
}
fn s(&mut self, i: usize, j: usize, v: f64) {
self.a[(i - 1) * self.n + (j - 1)] = v;
}
}
fn elmhes(m: &mut M<'_>) {
let n = m.n;
for p in 2..n {
let (mut x, mut i) = (0.0_f64, p);
for j in p..=n {
if fabs(m.g(j, p - 1)) > fabs(x) {
x = m.g(j, p - 1);
i = j;
}
}
if i != p {
for j in p - 1..=n {
let (u, v) = (m.g(i, j), m.g(p, j));
m.s(i, j, v);
m.s(p, j, u);
}
for j in 1..=n {
let (u, v) = (m.g(j, i), m.g(j, p));
m.s(j, i, v);
m.s(j, p, u);
}
}
if x != 0.0 {
for i in p + 1..=n {
let y = m.g(i, p - 1) / x;
if y != 0.0 {
m.s(i, p - 1, y);
for j in p..=n {
m.s(i, j, m.g(i, j) - y * m.g(p, j));
}
for j in 1..=n {
m.s(j, p, m.g(j, p) + y * m.g(j, i));
}
}
}
}
}
}
pub(super) fn eigenvalues(d: &[f64], n: usize) -> Option<(Vec<f64>, Vec<f64>)> {
let mut a = d.to_vec();
let mut m = M { a: &mut a, n };
elmhes(&mut m);
for i in 3..=n {
for j in 1..i - 1 {
m.s(i, j, 0.0);
}
}
hqr(&mut m)
}
fn hqr(m: &mut M<'_>) -> Option<(Vec<f64>, Vec<f64>)> {
let n = m.n;
let (mut wr, mut wi) = (vec![0.0; n + 1], vec![0.0; n + 1]);
let mut anorm = 0.0;
for i in 1..=n {
for j in i.saturating_sub(1).max(1)..=n {
anorm += fabs(m.g(i, j));
}
}
let (mut nn, mut t) = (n, 0.0_f64);
while nn >= 1 {
let mut its = 0;
loop {
let l = small_subdiagonal(m, nn, anorm);
let x = m.g(nn, nn);
if l == nn {
wr[nn] = x + t;
wi[nn] = 0.0;
nn -= 1;
} else if l == nn - 1 {
let roots = two_roots(m, nn, t);
(wr[nn - 1], wr[nn], wi[nn - 1], wi[nn]) = roots;
nn -= 2;
} else {
if its == 60 {
return None;
}
let shift = if its == 10 || its == 20 {
exceptional_shift(m, nn, &mut t)
} else {
(x, m.g(nn - 1, nn - 1), m.g(nn, nn - 1) * m.g(nn - 1, nn))
};
its += 1;
let (mm, pqr) = sweep_start(m, (l, nn), shift);
sweep_apply(m, (l, nn), mm, pqr);
}
if l + 1 >= nn {
break;
}
}
}
Some((wr[1..].to_vec(), wi[1..].to_vec()))
}
fn small_subdiagonal(m: &mut M<'_>, nn: usize, anorm: f64) -> usize {
let mut l = nn;
while l >= 2 {
let mut s = fabs(m.g(l - 1, l - 1)) + fabs(m.g(l, l));
if s == 0.0 {
s = anorm;
}
if fabs(m.g(l, l - 1)) + s == s {
m.s(l, l - 1, 0.0);
break;
}
l -= 1;
}
l
}
fn two_roots(m: &M<'_>, nn: usize, t: f64) -> (f64, f64, f64, f64) {
let (x, y) = (m.g(nn, nn), m.g(nn - 1, nn - 1));
let w = m.g(nn, nn - 1) * m.g(nn - 1, nn);
let p = 0.5 * (y - x);
let q = p * p + w;
let z = sqrt(fabs(q));
let x = x + t;
if q >= 0.0 {
let z = p + copysign(z, p);
let second = if z == 0.0 { x + z } else { x - w / z };
(x + z, second, 0.0, 0.0)
} else {
(x + p, x + p, -z, z)
}
}
fn exceptional_shift(m: &mut M<'_>, nn: usize, t: &mut f64) -> (f64, f64, f64) {
let x = m.g(nn, nn);
*t += x;
for i in 1..=nn {
m.s(i, i, m.g(i, i) - x);
}
let s = fabs(m.g(nn, nn - 1)) + fabs(m.g(nn - 1, nn - 2));
(0.75 * s, 0.75 * s, -0.4375 * s * s)
}
fn sweep_start(
m: &M<'_>,
(l, nn): (usize, usize),
(x, y, w): (f64, f64, f64),
) -> (usize, [f64; 3]) {
let mut mm = nn - 2;
loop {
let z = m.g(mm, mm);
let (rr, s) = (x - z, y - z);
let p = (rr * s - w) / m.g(mm + 1, mm) + m.g(mm, mm + 1);
let q = m.g(mm + 1, mm + 1) - z - rr - s;
let r = m.g(mm + 2, mm + 1);
let s = fabs(p) + fabs(q) + fabs(r);
let pqr = [p / s, q / s, r / s];
if mm == l {
return (mm, pqr);
}
let u = fabs(m.g(mm, mm - 1)) * (fabs(pqr[1]) + fabs(pqr[2]));
let v = fabs(pqr[0]) * (fabs(m.g(mm - 1, mm - 1)) + fabs(z) + fabs(m.g(mm + 1, mm + 1)));
if u + v == v {
return (mm, pqr);
}
mm -= 1;
}
}
fn sweep_apply(m: &mut M<'_>, (l, nn): (usize, usize), mm: usize, [p0, q0, r0]: [f64; 3]) {
for i in mm + 2..=nn {
m.s(i, i - 2, 0.0);
if i != mm + 2 {
m.s(i, i - 3, 0.0);
}
}
let (mut p, mut q, mut r, mut x) = (p0, q0, r0, 0.0);
for k in mm..nn {
if k != mm {
p = m.g(k, k - 1);
q = m.g(k + 1, k - 1);
r = if k == nn - 1 { 0.0 } else { m.g(k + 2, k - 1) };
x = fabs(p) + fabs(q) + fabs(r);
if x != 0.0 {
(p, q, r) = (p / x, q / x, r / x);
}
}
let s = copysign(sqrt(p * p + q * q + r * r), p);
if s == 0.0 {
continue;
}
if k == mm {
if l != mm {
m.s(k, k - 1, -m.g(k, k - 1));
}
} else {
m.s(k, k - 1, -s * x);
}
p += s;
let v = [p / s, q / s, r / s];
(q, r) = (q / p, r / p);
reflect(m, (k, l, nn), v, (q, r));
}
}
fn reflect(
m: &mut M<'_>,
(k, l, nn): (usize, usize, usize),
[x, y, z]: [f64; 3],
(q, r): (f64, f64),
) {
let three = k != nn - 1;
for j in k..=nn {
let mut p = m.g(k, j) + q * m.g(k + 1, j);
if three {
p += r * m.g(k + 2, j);
m.s(k + 2, j, m.g(k + 2, j) - p * z);
}
m.s(k + 1, j, m.g(k + 1, j) - p * y);
m.s(k, j, m.g(k, j) - p * x);
}
for i in l..=nn.min(k + 3) {
let mut p = x * m.g(i, k) + y * m.g(i, k + 1);
if three {
p += z * m.g(i, k + 2);
m.s(i, k + 2, m.g(i, k + 2) - p * r);
}
m.s(i, k + 1, m.g(i, k + 1) - p * q);
m.s(i, k, m.g(i, k) - p);
}
}
pub(super) fn null_space(a: &[f64], n: usize, nullity: usize) -> Vec<Vec<f64>> {
let mut u = a.to_vec();
let mut cols: Vec<usize> = (0..n).collect();
let mut rank = 0;
while rank < n - nullity.min(n) {
let (mut bi, mut bj, mut best) = (rank, rank, 0.0_f64);
for i in rank..n {
for j in rank..n {
if fabs(u[i * n + j]) > best {
(bi, bj, best) = (i, j, fabs(u[i * n + j]));
}
}
}
#[allow(clippy::neg_cmp_op_on_partial_ord)] if !(best > 0.0) {
break;
}
for c in 0..n {
u.swap(rank * n + c, bi * n + c);
}
for r in 0..n {
u.swap(r * n + rank, r * n + bj);
}
cols.swap(rank, bj);
for i in rank + 1..n {
let f = u[i * n + rank] / u[rank * n + rank];
for c in rank..n {
u[i * n + c] -= f * u[rank * n + c];
}
}
rank += 1;
}
back_substitute(&u, n, rank, &cols)
}
fn back_substitute(u: &[f64], n: usize, rank: usize, cols: &[usize]) -> Vec<Vec<f64>> {
(rank..n)
.map(|free| {
let mut x = vec![0.0; n];
x[free] = 1.0;
for r in (0..rank).rev() {
let s: f64 = (r + 1..n).map(|c| u[r * n + c] * x[c]).sum();
x[r] = -s / u[r * n + r];
}
let norm = sqrt(x.iter().map(|v| v * v).sum::<f64>());
let mut out = vec![0.0; n];
for (j, &c) in cols.iter().enumerate() {
out[c] = x[j] / norm;
}
out
})
.collect()
}