use oxiblas_core::scalar::{Field, Real, Scalar};
use oxiblas_matrix::{Mat, MatRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HessenbergError {
EmptyMatrix,
NotSquare,
}
impl core::fmt::Display for HessenbergError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::EmptyMatrix => write!(f, "Matrix is empty"),
Self::NotSquare => write!(f, "Matrix must be square"),
}
}
}
impl std::error::Error for HessenbergError {}
#[derive(Debug, Clone)]
pub struct Hessenberg<T: Scalar> {
q: Mat<T>,
h: Mat<T>,
n: usize,
}
impl<T: Field + Real + bytemuck::Zeroable> Hessenberg<T> {
pub fn compute(a: MatRef<'_, T>) -> Result<Self, HessenbergError> {
let m = a.nrows();
let n = a.ncols();
if m == 0 || n == 0 {
return Err(HessenbergError::EmptyMatrix);
}
if m != n {
return Err(HessenbergError::NotSquare);
}
if n == 1 {
let mut h = Mat::zeros(1, 1);
h[(0, 0)] = a[(0, 0)];
let mut q = Mat::zeros(1, 1);
q[(0, 0)] = T::one();
return Ok(Self { q, h, n });
}
if n == 2 {
let mut h = Mat::zeros(2, 2);
for i in 0..2 {
for j in 0..2 {
h[(i, j)] = a[(i, j)];
}
}
let mut q = Mat::zeros(2, 2);
q[(0, 0)] = T::one();
q[(1, 1)] = T::one();
return Ok(Self { q, h, n });
}
let mut h = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
h[(i, j)] = a[(i, j)];
}
}
let mut q = Mat::zeros(n, n);
for i in 0..n {
q[(i, i)] = T::one();
}
let mut v_storage: Vec<Vec<T>> = Vec::with_capacity(n - 2);
let mut tau_storage: Vec<T> = Vec::with_capacity(n - 2);
for k in 0..(n - 2) {
let mut x: Vec<T> = Vec::with_capacity(n - k - 1);
for i in (k + 1)..n {
x.push(h[(i, k)]);
}
let (v, tau) = householder_vector_with_tau(&x);
if tau != T::zero() {
for j in k..n {
let mut dot = T::zero();
for i in 0..v.len() {
dot = dot + v[i] * h[(k + 1 + i, j)];
}
let scaled = tau * dot;
for i in 0..v.len() {
h[(k + 1 + i, j)] = h[(k + 1 + i, j)] - scaled * v[i];
}
}
for i in 0..n {
let mut dot = T::zero();
for j in 0..v.len() {
dot = dot + h[(i, k + 1 + j)] * v[j];
}
let scaled = tau * dot;
for j in 0..v.len() {
h[(i, k + 1 + j)] = h[(i, k + 1 + j)] - scaled * v[j];
}
}
v_storage.push(v);
tau_storage.push(tau);
} else {
v_storage.push(vec![T::zero(); n - k - 1]);
tau_storage.push(T::zero());
}
}
for k in 0..(n - 2) {
let v = &v_storage[k];
let tau = tau_storage[k];
if tau != T::zero() {
for i in 0..n {
let mut dot = T::zero();
for j in 0..v.len() {
dot = dot + q[(i, k + 1 + j)] * v[j];
}
let scaled = tau * dot;
for j in 0..v.len() {
q[(i, k + 1 + j)] = q[(i, k + 1 + j)] - scaled * v[j];
}
}
}
}
let eps = <T as Scalar>::epsilon() * T::from_f64(100.0).unwrap_or(T::one());
for j in 0..(n - 2) {
for i in (j + 2)..n {
if Scalar::abs(h[(i, j)]) < eps {
h[(i, j)] = T::zero();
}
}
}
Ok(Self { q, h, n })
}
pub fn compute_auto(a: MatRef<'_, T>) -> Result<Self, HessenbergError> {
const AUTO_BLOCK_THRESHOLD: usize = 96;
let n = a.nrows();
if n >= AUTO_BLOCK_THRESHOLD {
Self::compute_blocked(a)
} else {
Self::compute(a)
}
}
pub fn compute_blocked(a: MatRef<'_, T>) -> Result<Self, HessenbergError> {
let nb = crate::workspace::optimal_block_size_hessenberg(a.nrows());
Self::compute_blocked_with_block_size(a, nb)
}
pub fn compute_blocked_with_block_size(
a: MatRef<'_, T>,
block_size: usize,
) -> Result<Self, HessenbergError> {
let m = a.nrows();
let n = a.ncols();
if m == 0 || n == 0 {
return Err(HessenbergError::EmptyMatrix);
}
if m != n {
return Err(HessenbergError::NotSquare);
}
if n <= block_size || n <= 3 {
return Self::compute(a);
}
let mut h = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
h[(i, j)] = a[(i, j)];
}
}
let mut q = Mat::zeros(n, n);
for i in 0..n {
q[(i, i)] = T::one();
}
let nb = block_size.min(n - 2);
let mut k = 0;
while k < n - 2 {
let kb = nb.min(n - 2 - k);
let mut v_block: Vec<Vec<T>> = Vec::with_capacity(kb);
let mut tau_block: Vec<T> = Vec::with_capacity(kb);
for j in 0..kb {
let col_idx = k + j;
let len = n - col_idx - 1;
let mut x = vec![T::zero(); len];
for i in 0..len {
x[i] = h[(col_idx + 1 + i, col_idx)];
}
let (v, tau) = householder_vector_with_tau(&x);
tau_block.push(tau);
if tau != T::zero() {
for jj in col_idx..n {
let mut dot = T::zero();
for i in 0..v.len() {
dot = dot + v[i] * h[(col_idx + 1 + i, jj)];
}
let scaled = tau * dot;
for i in 0..v.len() {
h[(col_idx + 1 + i, jj)] = h[(col_idx + 1 + i, jj)] - scaled * v[i];
}
}
for ii in 0..n {
let mut dot = T::zero();
for i in 0..v.len() {
dot = dot + h[(ii, col_idx + 1 + i)] * v[i];
}
let scaled = tau * dot;
for i in 0..v.len() {
h[(ii, col_idx + 1 + i)] = h[(ii, col_idx + 1 + i)] - scaled * v[i];
}
}
}
v_block.push(v);
}
for j in 0..kb {
let col_idx = k + j;
let v = &v_block[j];
let tau = tau_block[j];
if tau != T::zero() {
for row in 0..n {
let mut dot = T::zero();
for i in 0..v.len() {
dot = dot + q[(row, col_idx + 1 + i)] * v[i];
}
let scaled = tau * dot;
for i in 0..v.len() {
q[(row, col_idx + 1 + i)] = q[(row, col_idx + 1 + i)] - scaled * v[i];
}
}
}
}
k += kb;
}
let eps = <T as Scalar>::epsilon() * T::from_f64(100.0).unwrap_or(T::one());
for j in 0..(n - 2) {
for i in (j + 2)..n {
if Scalar::abs(h[(i, j)]) < eps {
h[(i, j)] = T::zero();
}
}
}
Ok(Self { q, h, n })
}
pub fn q(&self) -> MatRef<'_, T> {
self.q.as_ref()
}
pub fn h(&self) -> MatRef<'_, T> {
self.h.as_ref()
}
pub fn reconstruct(&self) -> Mat<T> {
let mut qh = Mat::zeros(self.n, self.n);
let mut a = Mat::zeros(self.n, self.n);
for i in 0..self.n {
for j in 0..self.n {
let mut sum = T::zero();
for k in 0..self.n {
sum = sum + self.q[(i, k)] * self.h[(k, j)];
}
qh[(i, j)] = sum;
}
}
for i in 0..self.n {
for j in 0..self.n {
let mut sum = T::zero();
for k in 0..self.n {
sum = sum + qh[(i, k)] * self.q[(j, k)];
}
a[(i, j)] = sum;
}
}
a
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Side {
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trans {
NoTrans,
Trans,
ConjTrans,
}
#[derive(Debug, Clone)]
pub struct HessenbergFactors<T: Scalar> {
factors: Mat<T>,
tau: Vec<T>,
n: usize,
ilo: usize,
ihi: usize,
}
impl<T: Field + Real + bytemuck::Zeroable> HessenbergFactors<T> {
#[inline]
pub fn n(&self) -> usize {
self.n
}
#[inline]
pub fn ilo(&self) -> usize {
self.ilo
}
#[inline]
pub fn ihi(&self) -> usize {
self.ihi
}
pub fn tau(&self) -> &[T] {
&self.tau
}
pub fn factors(&self) -> MatRef<'_, T> {
self.factors.as_ref()
}
#[must_use]
pub fn h(&self) -> Mat<T> {
let mut h = Mat::zeros(self.n, self.n);
for i in 0..self.n {
let j_start = if i > 0 { i - 1 } else { 0 };
for j in j_start..self.n {
h[(i, j)] = self.factors[(i, j)];
}
}
h
}
}
pub fn gehrd<T: Field + Real + bytemuck::Zeroable>(
a: MatRef<'_, T>,
) -> Result<HessenbergFactors<T>, HessenbergError> {
gehrd_range(a, 0, a.nrows())
}
pub fn gehrd_range<T: Field + Real + bytemuck::Zeroable>(
a: MatRef<'_, T>,
ilo: usize,
ihi: usize,
) -> Result<HessenbergFactors<T>, HessenbergError> {
let m = a.nrows();
let n = a.ncols();
if m == 0 || n == 0 {
return Err(HessenbergError::EmptyMatrix);
}
if m != n {
return Err(HessenbergError::NotSquare);
}
let ihi = ihi.min(n);
let ilo = ilo.min(ihi);
let mut factors = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
factors[(i, j)] = a[(i, j)];
}
}
let nh = if ihi > ilo + 1 { ihi - ilo - 1 } else { 0 };
let mut tau = vec![T::zero(); nh];
if nh == 0 {
return Ok(HessenbergFactors {
factors,
tau,
n,
ilo,
ihi,
});
}
for k in ilo..(ihi.saturating_sub(1)) {
let tau_idx = k - ilo;
if tau_idx >= tau.len() {
break;
}
let len = ihi - k - 1;
if len == 0 {
continue;
}
let mut x = vec![T::zero(); len];
for i in 0..len {
x[i] = factors[(k + 1 + i, k)];
}
let (v, tau_val) = householder_vector_lapack(&x);
tau[tau_idx] = tau_val;
if tau_val == T::zero() {
continue;
}
let beta = -sign_of(x[0]) * norm(&x);
factors[(k + 1, k)] = beta;
for i in 1..len {
factors[(k + 1 + i, k)] = v[i];
}
for j in (k + 1)..n {
let mut dot = factors[(k + 1, j)]; for i in 1..len {
dot = dot + v[i].conj() * factors[(k + 1 + i, j)];
}
let scaled = tau_val * dot;
factors[(k + 1, j)] = factors[(k + 1, j)] - scaled;
for i in 1..len {
factors[(k + 1 + i, j)] = factors[(k + 1 + i, j)] - scaled * v[i];
}
}
for i in 0..ihi {
let mut dot = factors[(i, k + 1)]; for jj in 1..len {
dot = dot + factors[(i, k + 1 + jj)] * v[jj];
}
let scaled = tau_val * dot;
factors[(i, k + 1)] = factors[(i, k + 1)] - scaled;
for jj in 1..len {
factors[(i, k + 1 + jj)] = factors[(i, k + 1 + jj)] - scaled * v[jj].conj();
}
}
}
Ok(HessenbergFactors {
factors,
tau,
n,
ilo,
ihi,
})
}
pub fn orghr<T: Field + Real + bytemuck::Zeroable>(
factors: &HessenbergFactors<T>,
) -> Result<Mat<T>, HessenbergError> {
let n = factors.n;
let ilo = factors.ilo;
let ihi = factors.ihi;
let mut q = Mat::zeros(n, n);
for i in 0..n {
q[(i, i)] = T::one();
}
let nh = if ihi > ilo + 1 { ihi - ilo - 1 } else { 0 };
if nh == 0 {
return Ok(q);
}
for k_idx in 0..nh {
let k = ilo + k_idx;
let len = ihi - k - 1;
if len == 0 || factors.tau[k_idx] == T::zero() {
continue;
}
let mut v = vec![T::one(); len]; for i in 1..len {
v[i] = factors.factors[(k + 1 + i, k)];
}
let tau_val = factors.tau[k_idx];
for row in 0..n {
let mut dot = q[(row, k + 1)]; for i in 1..len {
dot = dot + q[(row, k + 1 + i)] * v[i];
}
let scaled = tau_val * dot;
q[(row, k + 1)] = q[(row, k + 1)] - scaled;
for i in 1..len {
q[(row, k + 1 + i)] = q[(row, k + 1 + i)] - scaled * v[i].conj();
}
}
}
Ok(q)
}
pub fn unghr<T: Field + Real + bytemuck::Zeroable>(
factors: &HessenbergFactors<T>,
) -> Result<Mat<T>, HessenbergError> {
orghr(factors)
}
pub fn ormhr<T: Field + Real + bytemuck::Zeroable>(
factors: &HessenbergFactors<T>,
side: Side,
trans: Trans,
c: MatRef<'_, T>,
) -> Result<Mat<T>, HessenbergError> {
let n = factors.n;
let ilo = factors.ilo;
let ihi = factors.ihi;
let c_rows = c.nrows();
let c_cols = c.ncols();
match side {
Side::Left => {
if c_rows != n {
return Err(HessenbergError::NotSquare); }
}
Side::Right => {
if c_cols != n {
return Err(HessenbergError::NotSquare); }
}
}
let mut result = Mat::zeros(c_rows, c_cols);
for i in 0..c_rows {
for j in 0..c_cols {
result[(i, j)] = c[(i, j)];
}
}
let nh = if ihi > ilo + 1 { ihi - ilo - 1 } else { 0 };
if nh == 0 {
return Ok(result);
}
let (k_range, forward): (Vec<usize>, bool) = match (side, trans) {
(Side::Left, Trans::NoTrans) => ((0..nh).collect(), true),
(Side::Left, Trans::Trans) | (Side::Left, Trans::ConjTrans) => {
((0..nh).rev().collect(), false)
}
(Side::Right, Trans::NoTrans) => ((0..nh).rev().collect(), false),
(Side::Right, Trans::Trans) | (Side::Right, Trans::ConjTrans) => ((0..nh).collect(), true),
};
let _ = forward;
for k_idx in k_range {
let k = ilo + k_idx;
let len = ihi - k - 1;
if len == 0 || factors.tau[k_idx] == T::zero() {
continue;
}
let mut v = vec![T::one(); len]; for i in 1..len {
v[i] = factors.factors[(k + 1 + i, k)];
}
let tau_val = match trans {
Trans::NoTrans => factors.tau[k_idx],
Trans::Trans | Trans::ConjTrans => factors.tau[k_idx].conj(),
};
match side {
Side::Left => {
for j in 0..c_cols {
let mut dot = result[(k + 1, j)]; for i in 1..len {
let v_elem = match trans {
Trans::NoTrans => v[i].conj(),
Trans::Trans | Trans::ConjTrans => v[i],
};
dot = dot + v_elem * result[(k + 1 + i, j)];
}
let scaled = tau_val * dot;
result[(k + 1, j)] = result[(k + 1, j)] - scaled;
for i in 1..len {
let v_elem = match trans {
Trans::NoTrans => v[i],
Trans::Trans | Trans::ConjTrans => v[i].conj(),
};
result[(k + 1 + i, j)] = result[(k + 1 + i, j)] - scaled * v_elem;
}
}
}
Side::Right => {
for i in 0..c_rows {
let mut dot = result[(i, k + 1)]; for jj in 1..len {
let v_elem = match trans {
Trans::NoTrans => v[jj],
Trans::Trans | Trans::ConjTrans => v[jj].conj(),
};
dot = dot + result[(i, k + 1 + jj)] * v_elem;
}
let scaled = tau_val * dot;
result[(i, k + 1)] = result[(i, k + 1)] - scaled;
for jj in 1..len {
let v_elem = match trans {
Trans::NoTrans => v[jj].conj(),
Trans::Trans | Trans::ConjTrans => v[jj],
};
result[(i, k + 1 + jj)] = result[(i, k + 1 + jj)] - scaled * v_elem;
}
}
}
}
}
Ok(result)
}
pub fn unmhr<T: Field + Real + bytemuck::Zeroable>(
factors: &HessenbergFactors<T>,
side: Side,
trans: Trans,
c: MatRef<'_, T>,
) -> Result<Mat<T>, HessenbergError> {
ormhr(factors, side, trans, c)
}
fn sign_of<T: Field + Real>(x: T) -> T {
if x >= T::zero() { T::one() } else { -T::one() }
}
fn norm<T: Field + Real>(x: &[T]) -> T {
let mut sum = T::zero();
for &xi in x {
sum = sum + xi * xi.conj();
}
Real::sqrt(sum)
}
fn householder_vector_lapack<T: Field + Real>(x: &[T]) -> (Vec<T>, T) {
let n = x.len();
if n == 0 {
return (Vec::new(), T::zero());
}
if n == 1 {
return (vec![T::one()], T::zero());
}
let x_norm = norm(x);
if x_norm == T::zero() {
return (vec![T::zero(); n], T::zero());
}
let beta = -sign_of(x[0]) * x_norm;
let denom = x[0] - beta;
let mut v = vec![T::one(); n];
if Scalar::abs(denom) > <T as Scalar>::epsilon() {
for i in 1..n {
v[i] = x[i] / denom;
}
}
let tau = (beta - x[0]) / beta;
(v, tau)
}
fn householder_vector_with_tau<T: Field + Real>(x: &[T]) -> (Vec<T>, T) {
let n = x.len();
if n == 0 {
return (Vec::new(), T::zero());
}
let mut norm_sq = T::zero();
for i in 0..n {
norm_sq = norm_sq + x[i] * x[i];
}
let norm = Real::sqrt(norm_sq);
if norm == T::zero() {
return (vec![T::zero(); n], T::zero());
}
let sign = if x[0] >= T::zero() {
-T::one()
} else {
T::one()
};
let beta = sign * norm;
let mut v = x.to_vec();
v[0] = x[0] - beta;
let v_norm_sq = v[0] * v[0] + (norm_sq - x[0] * x[0]);
if v_norm_sq == T::zero() {
return (vec![T::zero(); n], T::zero());
}
let tau = T::from_f64(2.0).unwrap_or_else(T::zero) / v_norm_sq;
(v, tau)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_hessenberg_3x3() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let hess = Hessenberg::compute(a.as_ref()).unwrap();
let h = hess.h();
assert!(approx_eq(h[(2, 0)], 0.0, 1e-10));
let reconstructed = hess.reconstruct();
for i in 0..3 {
for j in 0..3 {
assert!(
approx_eq(reconstructed[(i, j)], a[(i, j)], 1e-10),
"reconstructed[{},{}] = {}, a = {}",
i,
j,
reconstructed[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_hessenberg_4x4() {
let a = Mat::from_rows(&[
&[4.0f64, 1.0, -2.0, 2.0],
&[1.0, 2.0, 0.0, 1.0],
&[-2.0, 0.0, 3.0, -2.0],
&[2.0, 1.0, -2.0, -1.0],
]);
let hess = Hessenberg::compute(a.as_ref()).unwrap();
let h = hess.h();
assert!(approx_eq(h[(2, 0)], 0.0, 1e-10));
assert!(approx_eq(h[(3, 0)], 0.0, 1e-10));
assert!(approx_eq(h[(3, 1)], 0.0, 1e-10));
let reconstructed = hess.reconstruct();
for i in 0..4 {
for j in 0..4 {
assert!(
approx_eq(reconstructed[(i, j)], a[(i, j)], 1e-10),
"reconstructed[{},{}] = {}, a = {}",
i,
j,
reconstructed[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_hessenberg_identity() {
let eye = Mat::from_rows(&[&[1.0f64, 0.0, 0.0], &[0.0, 1.0, 0.0], &[0.0, 0.0, 1.0]]);
let hess = Hessenberg::compute(eye.as_ref()).unwrap();
let h = hess.h();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(approx_eq(h[(i, j)], expected, 1e-10));
}
}
}
#[test]
fn test_hessenberg_q_orthogonal() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let hess = Hessenberg::compute(a.as_ref()).unwrap();
let q = hess.q();
let n = 3;
for i in 0..n {
for j in 0..n {
let mut dot = 0.0;
for k in 0..n {
dot += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(dot, expected, 1e-10),
"Q^T*Q[{},{}] = {}, expected {}",
i,
j,
dot,
expected
);
}
}
}
#[test]
fn test_hessenberg_small() {
let a = Mat::from_rows(&[&[5.0f64]]);
let hess = Hessenberg::compute(a.as_ref()).unwrap();
assert!(approx_eq(hess.h()[(0, 0)], 5.0, 1e-10));
let a2 = Mat::from_rows(&[&[1.0f64, 2.0], &[3.0, 4.0]]);
let hess2 = Hessenberg::compute(a2.as_ref()).unwrap();
for i in 0..2 {
for j in 0..2 {
assert!(approx_eq(hess2.h()[(i, j)], a2[(i, j)], 1e-10));
}
}
}
#[test]
fn test_hessenberg_f32() {
let a = Mat::from_rows(&[&[1.0f32, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let hess = Hessenberg::compute(a.as_ref()).unwrap();
let reconstructed = hess.reconstruct();
for i in 0..3 {
for j in 0..3 {
assert!(
(reconstructed[(i, j)] - a[(i, j)]).abs() < 1e-5,
"reconstructed[{},{}] = {}, a = {}",
i,
j,
reconstructed[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_gehrd_3x3() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let h = factors.h();
assert!(
h[(2, 0)].abs() < 1e-10,
"H[2,0] = {} should be zero",
h[(2, 0)]
);
}
#[test]
fn test_gehrd_4x4() {
let a = Mat::from_rows(&[
&[4.0f64, 1.0, -2.0, 2.0],
&[1.0, 2.0, 0.0, 1.0],
&[-2.0, 0.0, 3.0, -2.0],
&[2.0, 1.0, -2.0, -1.0],
]);
let factors = gehrd(a.as_ref()).unwrap();
let h = factors.h();
assert!(
h[(2, 0)].abs() < 1e-10,
"H[2,0] = {} should be zero",
h[(2, 0)]
);
assert!(
h[(3, 0)].abs() < 1e-10,
"H[3,0] = {} should be zero",
h[(3, 0)]
);
assert!(
h[(3, 1)].abs() < 1e-10,
"H[3,1] = {} should be zero",
h[(3, 1)]
);
}
#[test]
fn test_orghr_orthogonal() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let q = orghr(&factors).unwrap();
let n = 3;
for i in 0..n {
for j in 0..n {
let mut dot = 0.0;
for k in 0..n {
dot += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(dot, expected, 1e-10),
"Q^T*Q[{},{}] = {}, expected {}",
i,
j,
dot,
expected
);
}
}
}
#[test]
fn test_gehrd_orghr_reconstruction() {
let a = Mat::from_rows(&[
&[4.0f64, 1.0, -2.0, 2.0],
&[1.0, 2.0, 0.0, 1.0],
&[-2.0, 0.0, 3.0, -2.0],
&[2.0, 1.0, -2.0, -1.0],
]);
let factors = gehrd(a.as_ref()).unwrap();
let h = factors.h();
let q = orghr(&factors).unwrap();
let n = 4;
let mut qh = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * h[(k, j)];
}
qh[(i, j)] = sum;
}
}
let mut reconstructed = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += qh[(i, k)] * q[(j, k)]; }
reconstructed[(i, j)] = sum;
}
}
for i in 0..n {
for j in 0..n {
assert!(
approx_eq(reconstructed[(i, j)], a[(i, j)], 1e-10),
"reconstructed[{},{}] = {}, a = {}",
i,
j,
reconstructed[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_ormhr_left_notrans() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let q = orghr(&factors).unwrap();
let c = Mat::from_rows(&[&[1.0f64], &[0.0], &[0.0]]);
let qc_ormhr = ormhr(&factors, Side::Left, Trans::NoTrans, c.as_ref()).unwrap();
let n = 3;
let mut qc_explicit = Mat::zeros(n, 1);
for i in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * c[(k, 0)];
}
qc_explicit[(i, 0)] = sum;
}
for i in 0..n {
assert!(
approx_eq(qc_ormhr[(i, 0)], qc_explicit[(i, 0)], 1e-10),
"ormhr[{}] = {}, explicit = {}",
i,
qc_ormhr[(i, 0)],
qc_explicit[(i, 0)]
);
}
}
#[test]
fn test_ormhr_left_trans() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let q = orghr(&factors).unwrap();
let c = Mat::from_rows(&[&[1.0f64], &[2.0], &[3.0]]);
let qtc_ormhr = ormhr(&factors, Side::Left, Trans::Trans, c.as_ref()).unwrap();
let n = 3;
let mut qtc_explicit = Mat::zeros(n, 1);
for i in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(k, i)] * c[(k, 0)]; }
qtc_explicit[(i, 0)] = sum;
}
for i in 0..n {
assert!(
approx_eq(qtc_ormhr[(i, 0)], qtc_explicit[(i, 0)], 1e-10),
"ormhr_trans[{}] = {}, explicit = {}",
i,
qtc_ormhr[(i, 0)],
qtc_explicit[(i, 0)]
);
}
}
#[test]
fn test_ormhr_right_notrans() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let q = orghr(&factors).unwrap();
let c = Mat::from_rows(&[&[1.0f64, 0.0, 0.0]]);
let cq_ormhr = ormhr(&factors, Side::Right, Trans::NoTrans, c.as_ref()).unwrap();
let n = 3;
let mut cq_explicit = Mat::zeros(1, n);
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += c[(0, k)] * q[(k, j)];
}
cq_explicit[(0, j)] = sum;
}
for j in 0..n {
assert!(
approx_eq(cq_ormhr[(0, j)], cq_explicit[(0, j)], 1e-10),
"ormhr_right[{}] = {}, explicit = {}",
j,
cq_ormhr[(0, j)],
cq_explicit[(0, j)]
);
}
}
#[test]
fn test_gehrd_identity() {
let eye: Mat<f64> = Mat::eye(4);
let factors = gehrd(eye.as_ref()).unwrap();
let q = orghr(&factors).unwrap();
let h = factors.h();
for i in 0..4 {
for j in 0..4 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(q[(i, j)], expected, 1e-10),
"Q[{},{}] = {}, expected {}",
i,
j,
q[(i, j)],
expected
);
assert!(
approx_eq(h[(i, j)], expected, 1e-10),
"H[{},{}] = {}, expected {}",
i,
j,
h[(i, j)],
expected
);
}
}
}
#[test]
fn test_gehrd_2x2() {
let a = Mat::from_rows(&[&[1.0f64, 2.0], &[3.0, 4.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let h = factors.h();
for i in 0..2 {
for j in 0..2 {
assert!(
approx_eq(h[(i, j)], a[(i, j)], 1e-10),
"H[{},{}] = {}, a = {}",
i,
j,
h[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_gehrd_f32() {
let a = Mat::from_rows(&[&[1.0f32, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let factors = gehrd(a.as_ref()).unwrap();
let h = factors.h();
let q = orghr(&factors).unwrap();
assert!(h[(2, 0)].abs() < 1e-5);
for i in 0..3 {
for j in 0..3 {
let mut dot = 0.0f32;
for k in 0..3 {
dot += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() < 1e-5,
"Q^T*Q[{},{}] = {}, expected {}",
i,
j,
dot,
expected
);
}
}
}
#[test]
fn test_hessenberg_blocked_4x4() {
let a = Mat::from_rows(&[
&[4.0f64, 1.0, -2.0, 2.0],
&[1.0, 2.0, 0.0, 1.0],
&[-2.0, 0.0, 3.0, -2.0],
&[2.0, 1.0, -2.0, -1.0],
]);
let hess = Hessenberg::compute_blocked(a.as_ref()).unwrap();
let h = hess.h();
assert!(approx_eq(h[(2, 0)], 0.0, 1e-10), "H[2,0] = {}", h[(2, 0)]);
assert!(approx_eq(h[(3, 0)], 0.0, 1e-10), "H[3,0] = {}", h[(3, 0)]);
assert!(approx_eq(h[(3, 1)], 0.0, 1e-10), "H[3,1] = {}", h[(3, 1)]);
let reconstructed = hess.reconstruct();
for i in 0..4 {
for j in 0..4 {
assert!(
approx_eq(reconstructed[(i, j)], a[(i, j)], 1e-10),
"reconstructed[{},{}] = {}, a = {}",
i,
j,
reconstructed[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_hessenberg_blocked_q_orthogonal() {
let a = Mat::from_rows(&[
&[1.0f64, 2.0, 3.0, 4.0],
&[5.0, 6.0, 7.0, 8.0],
&[9.0, 10.0, 11.0, 12.0],
&[13.0, 14.0, 15.0, 16.0],
]);
let hess = Hessenberg::compute_blocked(a.as_ref()).unwrap();
let q = hess.q();
let n = 4;
for i in 0..n {
for j in 0..n {
let mut dot = 0.0;
for k in 0..n {
dot += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(dot, expected, 1e-10),
"Q^T*Q[{},{}] = {}, expected {}",
i,
j,
dot,
expected
);
}
}
}
#[test]
fn test_hessenberg_blocked_vs_unblocked() {
let n = 100;
let mut a: Mat<f64> = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i * 17 + j * 31) % 100) as f64 / 100.0 + 0.1;
}
}
let hess_unblocked = Hessenberg::compute(a.as_ref()).unwrap();
let hess_blocked = Hessenberg::compute_blocked_with_block_size(a.as_ref(), 16).unwrap();
let rec_unblocked = hess_unblocked.reconstruct();
let rec_blocked = hess_blocked.reconstruct();
for i in 0..n {
for j in 0..n {
assert!(
approx_eq(rec_unblocked[(i, j)], a[(i, j)], 1e-9),
"unblocked reconstruction differs at ({},{}): {} vs {}",
i,
j,
rec_unblocked[(i, j)],
a[(i, j)]
);
assert!(
approx_eq(rec_blocked[(i, j)], a[(i, j)], 1e-9),
"blocked reconstruction differs at ({},{}): {} vs {}",
i,
j,
rec_blocked[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_hessenberg_blocked_small() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let hess = Hessenberg::compute_blocked(a.as_ref()).unwrap();
let h = hess.h();
assert!(approx_eq(h[(2, 0)], 0.0, 1e-10));
let reconstructed = hess.reconstruct();
for i in 0..3 {
for j in 0..3 {
assert!(
approx_eq(reconstructed[(i, j)], a[(i, j)], 1e-10),
"reconstructed[{},{}] = {}, a = {}",
i,
j,
reconstructed[(i, j)],
a[(i, j)]
);
}
}
}
#[test]
fn test_hessenberg_blocked_identity() {
let eye: Mat<f64> = Mat::eye(5);
let hess = Hessenberg::compute_blocked(eye.as_ref()).unwrap();
let h = hess.h();
for i in 0..5 {
for j in 0..5 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(h[(i, j)], expected, 1e-10),
"H[{},{}] = {}, expected {}",
i,
j,
h[(i, j)],
expected
);
}
}
}
}