use num_traits::Zero;
use oxiblas_core::scalar::{ComplexScalar, Field, Real, Scalar};
use oxiblas_matrix::{Mat, MatRef};
use super::{BidiagError, BidiagVect, Side, Trans};
#[derive(Debug, Clone)]
pub struct ComplexBidiagFactors<T: Scalar> {
work: Mat<T>,
#[allow(dead_code)]
d_complex: Vec<T>,
#[allow(dead_code)]
e_complex: Vec<T>,
tauq: Vec<T>,
taup: Vec<T>,
pub d: Vec<T::Real>,
pub e: Vec<T::Real>,
phase_u: Vec<T>,
phase_v: Vec<T>,
pub m: usize,
pub n: usize,
}
impl<T: Field + ComplexScalar + bytemuck::Zeroable> ComplexBidiagFactors<T>
where
T::Real: Real,
{
pub fn compute(a: MatRef<'_, T>) -> Result<Self, BidiagError> {
let m = a.nrows();
let n = a.ncols();
if m == 0 || n == 0 {
return Err(BidiagError::EmptyMatrix);
}
if m >= n {
Self::compute_tall(a)
} else {
Self::compute_wide(a)
}
}
fn compute_tall(a: MatRef<'_, T>) -> Result<Self, BidiagError> {
let m = a.nrows();
let n = a.ncols();
let mut work: Mat<T> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
work[(i, j)] = a[(i, j)];
}
}
let mut tauq: Vec<T> = vec![T::zero(); n];
let num_e = n.saturating_sub(1);
let mut taup: Vec<T> = vec![T::zero(); num_e];
let mut d_complex: Vec<T> = vec![T::zero(); n];
let mut e_complex: Vec<T> = vec![T::zero(); num_e];
for j in 0..n {
let (tau, alpha) = complex_householder_column(&mut work, j, j, m);
d_complex[j] = alpha;
tauq[j] = tau;
complex_apply_householder_left(&mut work, j, j, m, n, tau);
if j < n - 1 {
let (tau_r, alpha_r) = complex_householder_row(&mut work, j, j + 1, n);
e_complex[j] = alpha_r;
taup[j] = tau_r;
complex_apply_householder_right(&mut work, j, j + 1, m, n, tau_r);
}
}
let mut phase_u: Vec<T> = vec![T::one(); n];
let mut phase_v: Vec<T> = vec![T::one(); n];
let mut d: Vec<T::Real> = vec![T::Real::zero(); n];
let mut e: Vec<T::Real> = vec![T::Real::zero(); num_e];
let abs_d0 = d_complex[0].abs();
if abs_d0 > T::Real::epsilon() {
phase_v[0] = d_complex[0].conj() / T::from_real(abs_d0);
d[0] = abs_d0;
}
for j in 0..num_e {
let prod_e = phase_u[j].conj() * e_complex[j];
let abs_e = prod_e.abs();
if abs_e > T::Real::epsilon() {
phase_v[j + 1] = prod_e.conj() / T::from_real(abs_e);
e[j] = abs_e;
}
let prod_d = d_complex[j + 1] * phase_v[j + 1];
let abs_d = prod_d.abs();
if abs_d > T::Real::epsilon() {
phase_u[j + 1] = prod_d / T::from_real(abs_d);
d[j + 1] = abs_d;
}
}
Ok(Self {
work,
d_complex,
e_complex,
tauq,
taup,
d,
e,
phase_u,
phase_v,
m,
n,
})
}
fn compute_wide(a: MatRef<'_, T>) -> Result<Self, BidiagError> {
let m = a.nrows();
let n = a.ncols();
let mut work: Mat<T> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
work[(i, j)] = a[(i, j)];
}
}
let mut tauq: Vec<T> = vec![T::zero(); m];
let mut taup: Vec<T> = vec![T::zero(); m];
let mut d_complex: Vec<T> = vec![T::zero(); m];
let num_e = if m > 0 { m - 1 } else { 0 };
let mut e_complex: Vec<T> = vec![T::zero(); num_e];
for j in 0..m {
let (tau_p, alpha_d) = complex_householder_row(&mut work, j, j, n);
d_complex[j] = alpha_d;
taup[j] = tau_p;
complex_apply_householder_right(&mut work, j, j, m, n, tau_p);
if j < m - 1 {
let (tau_q, alpha_e) = complex_householder_column(&mut work, j, j + 1, m);
e_complex[j] = alpha_e;
tauq[j] = tau_q;
complex_apply_householder_left(&mut work, j, j + 1, m, n, tau_q);
}
}
if m > 0 {
tauq[m - 1] = T::zero();
}
let mut phase_u: Vec<T> = vec![T::one(); m];
let mut phase_v: Vec<T> = vec![T::one(); m];
let mut d: Vec<T::Real> = vec![T::Real::zero(); m];
let mut e: Vec<T::Real> = vec![T::Real::zero(); num_e];
let abs_d0 = if m > 0 {
d_complex[0].abs()
} else {
T::Real::zero()
};
if abs_d0 > T::Real::epsilon() {
phase_v[0] = d_complex[0].conj() / T::from_real(abs_d0);
d[0] = abs_d0;
}
for j in 0..num_e {
let prod_e = e_complex[j] * phase_v[j];
let abs_e = prod_e.abs();
if abs_e > T::Real::epsilon() {
phase_u[j + 1] = prod_e / T::from_real(abs_e);
e[j] = abs_e;
}
let prod_d = phase_u[j + 1].conj() * d_complex[j + 1];
let abs_d = prod_d.abs();
if abs_d > T::Real::epsilon() {
phase_v[j + 1] = prod_d.conj() / T::from_real(abs_d);
d[j + 1] = abs_d;
}
}
Ok(Self {
work,
d_complex,
e_complex,
tauq,
taup,
d,
e,
phase_u,
phase_v,
m,
n,
})
}
pub fn diagonal(&self) -> &[T::Real] {
&self.d
}
pub fn off_diagonal(&self) -> &[T::Real] {
&self.e
}
pub fn generate_u(&self) -> Result<Mat<T>, BidiagError> {
if self.m >= self.n {
self.generate_u_tall()
} else {
self.generate_u_wide()
}
}
pub fn generate_v(&self) -> Result<Mat<T>, BidiagError> {
if self.m >= self.n {
self.generate_v_tall()
} else {
self.generate_v_wide()
}
}
fn generate_u_tall(&self) -> Result<Mat<T>, BidiagError> {
let mut u: Mat<T> = Mat::zeros(self.m, self.m);
for i in 0..self.m {
u[(i, i)] = T::one();
}
for j in 0..self.n {
let tau = self.tauq[j];
if tau.abs() > T::Real::zero() {
for r in 0..self.m {
let mut w = u[(r, j)]; for i in (j + 1)..self.m {
w = w + u[(r, i)] * self.work[(i, j)];
}
let tw = tau * w;
u[(r, j)] = u[(r, j)] - tw;
for i in (j + 1)..self.m {
u[(r, i)] = u[(r, i)] - tw * self.work[(i, j)].conj();
}
}
}
}
let mut u_thin: Mat<T> = Mat::zeros(self.m, self.n);
for j in 0..self.n {
let p = self.phase_u[j];
for i in 0..self.m {
u_thin[(i, j)] = u[(i, j)] * p;
}
}
Ok(u_thin)
}
fn generate_u_wide(&self) -> Result<Mat<T>, BidiagError> {
let mut u: Mat<T> = Mat::zeros(self.m, self.m);
for i in 0..self.m {
u[(i, i)] = T::one();
}
let num_q = self.m.saturating_sub(1);
for j in 0..num_q {
let tau = self.tauq[j];
if tau.abs() > T::Real::zero() {
let start = j + 1;
for r in 0..self.m {
let mut w = u[(r, start)];
for i in (start + 1)..self.m {
w = w + u[(r, i)] * self.work[(i, j)];
}
let tw = tau * w;
u[(r, start)] = u[(r, start)] - tw;
for i in (start + 1)..self.m {
u[(r, i)] = u[(r, i)] - tw * self.work[(i, j)].conj();
}
}
}
}
for j in 0..self.m {
let p = self.phase_u[j];
for i in 0..self.m {
u[(i, j)] = u[(i, j)] * p;
}
}
Ok(u)
}
fn generate_v_tall(&self) -> Result<Mat<T>, BidiagError> {
let mut v: Mat<T> = Mat::zeros(self.n, self.n);
for i in 0..self.n {
v[(i, i)] = T::one();
}
let num_p = self.taup.len();
for j in 0..num_p {
let tau = self.taup[j];
if tau.abs() > T::Real::zero() {
let start = j + 1;
let tau_conj = tau.conj();
for r in 0..self.n {
let mut w = v[(r, start)]; for i in (start + 1)..self.n {
w = w + v[(r, i)] * self.work[(j, i)];
}
let tw = tau_conj * w;
v[(r, start)] = v[(r, start)] - tw;
for i in (start + 1)..self.n {
v[(r, i)] = v[(r, i)] - tw * self.work[(j, i)].conj();
}
}
}
}
for j in 0..self.n {
let p = self.phase_v[j];
for i in 0..self.n {
v[(i, j)] = v[(i, j)] * p;
}
}
Ok(v)
}
fn generate_v_wide(&self) -> Result<Mat<T>, BidiagError> {
let mut v: Mat<T> = Mat::zeros(self.n, self.n);
for i in 0..self.n {
v[(i, i)] = T::one();
}
for j in 0..self.m {
let tau = self.taup[j];
if tau.abs() > T::Real::zero() {
let tau_conj = tau.conj();
for r in 0..self.n {
let mut w = v[(r, j)]; for i in (j + 1)..self.n {
w = w + v[(r, i)] * self.work[(j, i)];
}
let tw = tau_conj * w;
v[(r, j)] = v[(r, j)] - tw;
for i in (j + 1)..self.n {
v[(r, i)] = v[(r, i)] - tw * self.work[(j, i)].conj();
}
}
}
}
for j in 0..self.m {
let p = self.phase_v[j];
for i in 0..self.n {
v[(i, j)] = v[(i, j)] * p;
}
}
Ok(v)
}
pub fn generate(&self, vect: BidiagVect) -> Result<Mat<T>, BidiagError> {
match vect {
BidiagVect::Q => self.generate_u(),
BidiagVect::P => self.generate_v(),
}
}
pub fn apply(
&self,
vect: BidiagVect,
side: Side,
trans: Trans,
c: MatRef<'_, T>,
) -> Result<Mat<T>, BidiagError> {
match vect {
BidiagVect::Q => self.apply_q(side, trans, c),
BidiagVect::P => self.apply_p(side, trans, c),
}
}
fn apply_q(&self, side: Side, trans: Trans, c: MatRef<'_, T>) -> Result<Mat<T>, BidiagError> {
match side {
Side::Left if c.nrows() != self.m => return Err(BidiagError::DimensionMismatch),
Side::Right if c.ncols() != self.m => return Err(BidiagError::DimensionMismatch),
_ => {}
}
let mut result = copy_matrix(c);
if self.m >= self.n {
self.apply_q_tall(&mut result, side, trans);
} else {
self.apply_q_wide(&mut result, side, trans);
}
Ok(result)
}
fn apply_p(&self, side: Side, trans: Trans, c: MatRef<'_, T>) -> Result<Mat<T>, BidiagError> {
match side {
Side::Left if c.nrows() != self.n => return Err(BidiagError::DimensionMismatch),
Side::Right if c.ncols() != self.n => return Err(BidiagError::DimensionMismatch),
_ => {}
}
let mut result = copy_matrix(c);
if self.m >= self.n {
self.apply_p_tall(&mut result, side, trans);
} else {
self.apply_p_wide(&mut result, side, trans);
}
Ok(result)
}
fn apply_q_tall(&self, c: &mut Mat<T>, side: Side, trans: Trans) {
let n = self.n;
let m = self.m;
match (side, trans) {
(Side::Left, Trans::NoTrans) => {
scale_rows(c, &self.phase_u, false);
for j in (0..n).rev() {
apply_h_col_left(c, &self.work, j, j, m, self.tauq[j]);
}
}
(Side::Left, Trans::Trans) => {
for j in 0..n {
apply_h_col_left(c, &self.work, j, j, m, self.tauq[j].conj());
}
scale_rows(c, &self.phase_u, true);
}
(Side::Right, Trans::NoTrans) => {
for j in 0..n {
apply_h_col_right(c, &self.work, j, j, m, self.tauq[j]);
}
scale_cols(c, &self.phase_u, false);
}
(Side::Right, Trans::Trans) => {
scale_cols(c, &self.phase_u, true);
for j in (0..n).rev() {
apply_h_col_right(c, &self.work, j, j, m, self.tauq[j].conj());
}
}
}
}
fn apply_q_wide(&self, c: &mut Mat<T>, side: Side, trans: Trans) {
let m = self.m;
let num_q = m.saturating_sub(1);
match (side, trans) {
(Side::Left, Trans::NoTrans) => {
scale_rows(c, &self.phase_u, false);
for j in (0..num_q).rev() {
apply_h_col_left(c, &self.work, j + 1, j, m, self.tauq[j]);
}
}
(Side::Left, Trans::Trans) => {
for j in 0..num_q {
apply_h_col_left(c, &self.work, j + 1, j, m, self.tauq[j].conj());
}
scale_rows(c, &self.phase_u, true);
}
(Side::Right, Trans::NoTrans) => {
for j in 0..num_q {
apply_h_col_right(c, &self.work, j + 1, j, m, self.tauq[j]);
}
scale_cols(c, &self.phase_u, false);
}
(Side::Right, Trans::Trans) => {
scale_cols(c, &self.phase_u, true);
for j in (0..num_q).rev() {
apply_h_col_right(c, &self.work, j + 1, j, m, self.tauq[j].conj());
}
}
}
}
fn apply_p_tall(&self, c: &mut Mat<T>, side: Side, trans: Trans) {
let n = self.n;
let np = self.taup.len();
match (side, trans) {
(Side::Left, Trans::NoTrans) => {
scale_rows(c, &self.phase_v, false);
for j in (0..np).rev() {
apply_h_row_left(c, &self.work, j + 1, j, n, self.taup[j].conj());
}
}
(Side::Left, Trans::Trans) => {
for j in 0..np {
apply_h_row_left(c, &self.work, j + 1, j, n, self.taup[j]);
}
scale_rows(c, &self.phase_v, true);
}
(Side::Right, Trans::NoTrans) => {
for j in 0..np {
apply_h_row_right(c, &self.work, j + 1, j, n, self.taup[j].conj());
}
scale_cols(c, &self.phase_v, false);
}
(Side::Right, Trans::Trans) => {
scale_cols(c, &self.phase_v, true);
for j in (0..np).rev() {
apply_h_row_right(c, &self.work, j + 1, j, n, self.taup[j]);
}
}
}
}
fn apply_p_wide(&self, c: &mut Mat<T>, side: Side, trans: Trans) {
let n = self.n;
let np = self.taup.len();
match (side, trans) {
(Side::Left, Trans::NoTrans) => {
scale_rows(c, &self.phase_v, false);
for j in (0..np).rev() {
apply_h_row_left(c, &self.work, j, j, n, self.taup[j].conj());
}
}
(Side::Left, Trans::Trans) => {
for j in 0..np {
apply_h_row_left(c, &self.work, j, j, n, self.taup[j]);
}
scale_rows(c, &self.phase_v, true);
}
(Side::Right, Trans::NoTrans) => {
for j in 0..np {
apply_h_row_right(c, &self.work, j, j, n, self.taup[j].conj());
}
scale_cols(c, &self.phase_v, false);
}
(Side::Right, Trans::Trans) => {
scale_cols(c, &self.phase_v, true);
for j in (0..np).rev() {
apply_h_row_right(c, &self.work, j, j, n, self.taup[j]);
}
}
}
}
}
fn complex_householder_column<T: Field + ComplexScalar>(
work: &mut Mat<T>,
col: usize,
start: usize,
m: usize,
) -> (T, T)
where
T::Real: Real,
{
let mut norm_sq = T::Real::zero();
for i in start..m {
norm_sq = norm_sq + work[(i, col)].abs_sq();
}
let norm = <T::Real as Real>::sqrt(norm_sq);
if norm <= T::Real::zero() {
return (T::zero(), T::zero());
}
let x0 = work[(start, col)];
let x0_abs = x0.abs();
let alpha = if x0_abs > T::Real::zero() {
let sign = T::from_real_imag(x0.real() / x0_abs, x0.imag() / x0_abs);
T::zero() - sign * T::from_real(norm)
} else {
T::from_real(-norm)
};
let tau = (alpha - x0) / alpha;
let denom = x0 - alpha;
if denom.abs() > T::Real::zero() {
let scale = T::one() / denom;
for i in (start + 1)..m {
work[(i, col)] = work[(i, col)] * scale;
}
}
(tau, alpha)
}
fn complex_householder_row<T: Field + ComplexScalar>(
work: &mut Mat<T>,
row: usize,
start: usize,
n: usize,
) -> (T, T)
where
T::Real: Real,
{
let mut norm_sq = T::Real::zero();
for i in start..n {
norm_sq = norm_sq + work[(row, i)].abs_sq();
}
let norm = <T::Real as Real>::sqrt(norm_sq);
if norm <= T::Real::zero() {
return (T::zero(), T::zero());
}
let y0 = work[(row, start)].conj();
let y0_abs = y0.abs();
let beta = if y0_abs > T::Real::zero() {
let sign = T::from_real_imag(y0.real() / y0_abs, y0.imag() / y0_abs);
T::zero() - sign * T::from_real(norm)
} else {
T::from_real(-norm)
};
let tau_h = (beta - y0) / beta;
let denom = y0 - beta;
if denom.abs() > T::Real::zero() {
let scale = T::one() / denom;
for i in (start + 1)..n {
work[(row, i)] = work[(row, i)].conj() * scale;
}
}
let alpha = beta.conj();
(tau_h, alpha)
}
fn complex_apply_householder_left<T: Field + ComplexScalar>(
work: &mut Mat<T>,
col_store: usize,
start: usize,
m: usize,
n: usize,
tau: T,
) where
T::Real: Real,
{
if tau.abs() < T::Real::epsilon() {
return;
}
for col in (col_store + 1)..n {
let mut w = work[(start, col)]; for i in (start + 1)..m {
w = w + work[(i, col_store)].conj() * work[(i, col)];
}
let tw = tau * w;
work[(start, col)] = work[(start, col)] - tw;
for i in (start + 1)..m {
work[(i, col)] = work[(i, col)] - tw * work[(i, col_store)];
}
}
}
fn complex_apply_householder_right<T: Field + ComplexScalar>(
work: &mut Mat<T>,
row_store: usize,
start: usize,
m: usize,
n: usize,
tau_h: T,
) where
T::Real: Real,
{
if tau_h.abs() < T::Real::epsilon() {
return;
}
let tau_conj = tau_h.conj();
for row in (row_store + 1)..m {
let mut dot = work[(row, start)]; for i in (start + 1)..n {
dot = dot + work[(row, i)] * work[(row_store, i)];
}
let tw = tau_conj * dot;
work[(row, start)] = work[(row, start)] - tw; for i in (start + 1)..n {
work[(row, i)] = work[(row, i)] - tw * work[(row_store, i)].conj();
}
}
}
pub fn complex_gebrd<T: Field + ComplexScalar + bytemuck::Zeroable>(
a: MatRef<'_, T>,
) -> Result<ComplexBidiagFactors<T>, BidiagError>
where
T::Real: Real,
{
ComplexBidiagFactors::compute(a)
}
fn copy_matrix<T: Field + bytemuck::Zeroable>(c: MatRef<'_, T>) -> Mat<T> {
let (rows, cols) = (c.nrows(), c.ncols());
let mut result: Mat<T> = Mat::zeros(rows, cols);
for i in 0..rows {
for j in 0..cols {
result[(i, j)] = c[(i, j)];
}
}
result
}
fn scale_rows<T: Field>(c: &mut Mat<T>, phases: &[T], conjugate: bool) {
let cols = c.ncols();
let limit = phases.len().min(c.nrows());
for (i, &phase) in phases.iter().take(limit).enumerate() {
let ph = if conjugate { phase.conj() } else { phase };
for cc in 0..cols {
c[(i, cc)] = c[(i, cc)] * ph;
}
}
}
fn scale_cols<T: Field>(c: &mut Mat<T>, phases: &[T], conjugate: bool) {
let rows = c.nrows();
let limit = phases.len().min(c.ncols());
for (j, &phase) in phases.iter().take(limit).enumerate() {
let ph = if conjugate { phase.conj() } else { phase };
for r in 0..rows {
c[(r, j)] = c[(r, j)] * ph;
}
}
}
fn apply_h_col_left<T: Field>(
c: &mut Mat<T>,
work: &Mat<T>,
piv: usize,
col: usize,
end: usize,
t: T,
) {
if t == T::zero() {
return;
}
let cols = c.ncols();
for cc in 0..cols {
let mut w = c[(piv, cc)]; for i in (piv + 1)..end {
w = w + work[(i, col)].conj() * c[(i, cc)];
}
let tw = t * w;
c[(piv, cc)] = c[(piv, cc)] - tw;
for i in (piv + 1)..end {
c[(i, cc)] = c[(i, cc)] - tw * work[(i, col)];
}
}
}
fn apply_h_col_right<T: Field>(
c: &mut Mat<T>,
work: &Mat<T>,
piv: usize,
col: usize,
end: usize,
t: T,
) {
if t == T::zero() {
return;
}
let rows = c.nrows();
for row in 0..rows {
let mut w = c[(row, piv)]; for i in (piv + 1)..end {
w = w + c[(row, i)] * work[(i, col)];
}
let tw = t * w;
c[(row, piv)] = c[(row, piv)] - tw;
for i in (piv + 1)..end {
c[(row, i)] = c[(row, i)] - tw * work[(i, col)].conj();
}
}
}
fn apply_h_row_left<T: Field>(
c: &mut Mat<T>,
work: &Mat<T>,
piv: usize,
row_store: usize,
end: usize,
t: T,
) {
if t == T::zero() {
return;
}
let cols = c.ncols();
for cc in 0..cols {
let mut w = c[(piv, cc)]; for i in (piv + 1)..end {
w = w + work[(row_store, i)].conj() * c[(i, cc)];
}
let tw = t * w;
c[(piv, cc)] = c[(piv, cc)] - tw;
for i in (piv + 1)..end {
c[(i, cc)] = c[(i, cc)] - tw * work[(row_store, i)];
}
}
}
fn apply_h_row_right<T: Field>(
c: &mut Mat<T>,
work: &Mat<T>,
piv: usize,
row_store: usize,
end: usize,
t: T,
) {
if t == T::zero() {
return;
}
let rows = c.nrows();
for row in 0..rows {
let mut w = c[(row, piv)]; for i in (piv + 1)..end {
w = w + c[(row, i)] * work[(row_store, i)];
}
let tw = t * w;
c[(row, piv)] = c[(row, piv)] - tw;
for i in (piv + 1)..end {
c[(row, i)] = c[(row, i)] - tw * work[(row_store, i)].conj();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_complex::{Complex32, Complex64};
fn assert_unitary_c64(mat: &Mat<Complex64>, label: &str, tol: f64) {
let n = mat.nrows();
let m = mat.ncols();
for i in 0..m {
for j in 0..m {
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..n {
sum = sum + mat[(k, i)].conj() * mat[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
let diff = (sum - Complex64::new(expected, 0.0)).norm();
assert!(
diff < tol,
"{}^H * {}[{},{}] error: {} (got ({:.6},{:.6}))",
label,
label,
i,
j,
diff,
sum.re,
sum.im,
);
}
}
}
fn assert_unitary_c32(mat: &Mat<Complex32>, label: &str, tol: f32) {
let n = mat.nrows();
let m = mat.ncols();
for i in 0..m {
for j in 0..m {
let mut sum = Complex32::new(0.0, 0.0);
for k in 0..n {
sum = sum + mat[(k, i)].conj() * mat[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
let diff = (sum - Complex32::new(expected, 0.0)).norm();
assert!(
diff < tol,
"{}^H * {}[{},{}] error: {}",
label,
label,
i,
j,
diff,
);
}
}
}
fn reconstruct_upper_c64(
u: &Mat<Complex64>,
d: &[f64],
e: &[f64],
v: &Mat<Complex64>,
m: usize,
n: usize,
) -> Mat<Complex64> {
let mut b: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..d.len() {
b[(i, i)] = Complex64::new(d[i], 0.0);
}
for i in 0..e.len() {
b[(i, i + 1)] = Complex64::new(e[i], 0.0);
}
let mut bvh: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..n {
sum = sum + b[(i, k)] * v[(j, k)].conj();
}
bvh[(i, j)] = sum;
}
}
let u_cols = u.ncols();
let mut result: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..u_cols {
sum = sum + u[(i, k)] * bvh[(k, j)];
}
result[(i, j)] = sum;
}
}
result
}
fn reconstruct_lower_c64(
u: &Mat<Complex64>,
d: &[f64],
e: &[f64],
v: &Mat<Complex64>,
m: usize,
n: usize,
) -> Mat<Complex64> {
let mut b: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..d.len() {
b[(i, i)] = Complex64::new(d[i], 0.0);
}
for i in 0..e.len() {
b[(i + 1, i)] = Complex64::new(e[i], 0.0);
}
let mut bvh: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..n {
sum = sum + b[(i, k)] * v[(j, k)].conj(); }
bvh[(i, j)] = sum;
}
}
let mut result: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..m {
sum = sum + u[(i, k)] * bvh[(k, j)];
}
result[(i, j)] = sum;
}
}
result
}
fn check_reconstruction(
reconstructed: &Mat<Complex64>,
original: &Mat<Complex64>,
tol: f64,
label: &str,
) {
let m = original.nrows();
let n = original.ncols();
for i in 0..m {
for j in 0..n {
let diff = (reconstructed[(i, j)] - original[(i, j)]).norm();
assert!(
diff < tol,
"{} A[{},{}] error: {} (got ({:.6},{:.6}), exp ({:.6},{:.6}))",
label,
i,
j,
diff,
reconstructed[(i, j)].re,
reconstructed[(i, j)].im,
original[(i, j)].re,
original[(i, j)].im,
);
}
}
}
#[test]
fn test_complex_bidiag_tall_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 2.0),
Complex64::new(3.0, -1.0),
Complex64::new(0.5, 0.5),
],
&[
Complex64::new(4.0, 0.0),
Complex64::new(5.0, 1.0),
Complex64::new(2.0, -2.0),
],
&[
Complex64::new(7.0, -1.0),
Complex64::new(8.0, 0.0),
Complex64::new(3.0, 1.0),
],
&[
Complex64::new(10.0, 0.5),
Complex64::new(11.0, -0.5),
Complex64::new(4.0, 0.0),
],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("compute");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c64(&u, "U", 1e-10);
assert_unitary_c64(&v, "V", 1e-10);
let rec = reconstruct_upper_c64(&u, &f.d, &f.e, &v, 4, 3);
check_reconstruction(&rec, &a, 1e-9, "Tall");
}
#[test]
fn test_complex_bidiag_square_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 1.0),
Complex64::new(2.0, -1.0),
Complex64::new(3.0, 0.5),
],
&[
Complex64::new(4.0, 0.0),
Complex64::new(5.0, 2.0),
Complex64::new(6.0, -1.0),
],
&[
Complex64::new(7.0, -0.5),
Complex64::new(8.0, 0.0),
Complex64::new(9.0, 1.0),
],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("compute");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c64(&u, "U", 1e-10);
assert_unitary_c64(&v, "V", 1e-10);
let rec = reconstruct_upper_c64(&u, &f.d, &f.e, &v, 3, 3);
check_reconstruction(&rec, &a, 1e-9, "Square");
}
#[test]
fn test_complex_bidiag_wide_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 1.0),
Complex64::new(2.0, 0.0),
Complex64::new(3.0, -1.0),
Complex64::new(4.0, 0.5),
],
&[
Complex64::new(5.0, 0.0),
Complex64::new(6.0, 1.0),
Complex64::new(7.0, 0.0),
Complex64::new(8.0, -0.5),
],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("compute");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c64(&u, "U", 1e-10);
assert_unitary_c64(&v, "V", 1e-10);
let rec = reconstruct_lower_c64(&u, &f.d, &f.e, &v, 2, 4);
check_reconstruction(&rec, &a, 1e-9, "Wide");
}
#[test]
fn test_complex_bidiag_1x1_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[&[Complex64::new(3.0, 4.0)]]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("1x1");
assert_eq!(f.d.len(), 1);
assert_eq!(f.e.len(), 0);
assert!((f.d[0] - 5.0).abs() < 1e-10, "d[0]={}", f.d[0]);
}
#[test]
fn test_complex_bidiag_hermitian_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[Complex64::new(4.0, 0.0), Complex64::new(1.0, -1.0)],
&[Complex64::new(1.0, 1.0), Complex64::new(3.0, 0.0)],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("hermitian");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c64(&u, "U", 1e-10);
assert_unitary_c64(&v, "V", 1e-10);
let rec = reconstruct_upper_c64(&u, &f.d, &f.e, &v, 2, 2);
check_reconstruction(&rec, &a, 1e-10, "Hermitian");
}
#[test]
fn test_complex_bidiag_identity_c64() {
let a: Mat<Complex64> = Mat::eye(3);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("identity");
for &di in &f.d {
assert!((di - 1.0).abs() < 1e-10, "diag={}", di);
}
for &ei in &f.e {
assert!(ei.abs() < 1e-10, "offdiag={}", ei);
}
}
#[test]
fn test_complex_bidiag_real_matrix_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 0.0),
Complex64::new(2.0, 0.0),
Complex64::new(3.0, 0.0),
],
&[
Complex64::new(4.0, 0.0),
Complex64::new(5.0, 0.0),
Complex64::new(6.0, 0.0),
],
&[
Complex64::new(7.0, 0.0),
Complex64::new(8.0, 0.0),
Complex64::new(9.0, 0.0),
],
&[
Complex64::new(10.0, 0.0),
Complex64::new(11.0, 0.0),
Complex64::new(12.0, 0.0),
],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("real");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c64(&u, "U", 1e-10);
assert_unitary_c64(&v, "V", 1e-10);
let rec = reconstruct_upper_c64(&u, &f.d, &f.e, &v, 4, 3);
check_reconstruction(&rec, &a, 1e-9, "Real");
}
#[test]
fn test_complex_bidiag_larger_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 0.3),
Complex64::new(2.0, -0.5),
Complex64::new(3.0, 0.1),
Complex64::new(4.0, -0.2),
],
&[
Complex64::new(5.0, 0.7),
Complex64::new(6.0, 0.0),
Complex64::new(7.0, -0.3),
Complex64::new(8.0, 0.4),
],
&[
Complex64::new(9.0, -0.1),
Complex64::new(10.0, 0.6),
Complex64::new(11.0, 0.0),
Complex64::new(12.0, -0.5),
],
&[
Complex64::new(13.0, 0.2),
Complex64::new(14.0, -0.4),
Complex64::new(15.0, 0.8),
Complex64::new(16.0, 0.0),
],
&[
Complex64::new(0.5, 1.0),
Complex64::new(1.5, -1.0),
Complex64::new(2.5, 0.5),
Complex64::new(3.5, -0.5),
],
&[
Complex64::new(4.5, 0.0),
Complex64::new(5.5, 0.3),
Complex64::new(6.5, -0.7),
Complex64::new(7.5, 0.1),
],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("larger");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c64(&u, "U", 1e-10);
assert_unitary_c64(&v, "V", 1e-10);
let rec = reconstruct_upper_c64(&u, &f.d, &f.e, &v, 6, 4);
check_reconstruction(&rec, &a, 1e-8, "Larger");
}
#[test]
fn test_complex_bidiag_c32() {
let a: Mat<Complex32> = Mat::from_rows(&[
&[Complex32::new(1.0, 1.0), Complex32::new(2.0, -1.0)],
&[Complex32::new(3.0, 0.0), Complex32::new(4.0, 0.5)],
&[Complex32::new(5.0, -0.5), Complex32::new(6.0, 0.0)],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("c32");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c32(&u, "U", 1e-4);
assert_unitary_c32(&v, "V", 1e-4);
let m = 3;
let n = 2;
let mut b: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..f.d.len() {
b[(i, i)] = Complex32::new(f.d[i], 0.0);
}
for i in 0..f.e.len() {
b[(i, i + 1)] = Complex32::new(f.e[i], 0.0);
}
let mut bvh: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex32::new(0.0, 0.0);
for k in 0..n {
sum = sum + b[(i, k)] * v[(j, k)].conj();
}
bvh[(i, j)] = sum;
}
}
let mut rec: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex32::new(0.0, 0.0);
for k in 0..u.ncols() {
sum = sum + u[(i, k)] * bvh[(k, j)];
}
rec[(i, j)] = sum;
}
}
for i in 0..m {
for j in 0..n {
let diff = (rec[(i, j)] - a[(i, j)]).norm();
assert!(diff < 1e-4, "C32 A[{},{}] error: {}", i, j, diff);
}
}
}
#[test]
fn test_complex_bidiag_wide_c32() {
let a: Mat<Complex32> = Mat::from_rows(&[
&[
Complex32::new(1.0, 0.5),
Complex32::new(2.0, 0.0),
Complex32::new(3.0, -0.5),
Complex32::new(4.0, 0.3),
],
&[
Complex32::new(5.0, 0.0),
Complex32::new(6.0, -0.3),
Complex32::new(7.0, 0.0),
Complex32::new(8.0, 0.5),
],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("wide c32");
let u = f.generate_u().expect("u");
let v = f.generate_v().expect("v");
assert_unitary_c32(&u, "U", 1e-4);
assert_unitary_c32(&v, "V", 1e-4);
let m = 2;
let n = 4;
let mut b: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..f.d.len() {
b[(i, i)] = Complex32::new(f.d[i], 0.0);
}
for i in 0..f.e.len() {
b[(i + 1, i)] = Complex32::new(f.e[i], 0.0);
}
let mut bvh: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex32::new(0.0, 0.0);
for k in 0..n {
sum = sum + b[(i, k)] * v[(j, k)].conj();
}
bvh[(i, j)] = sum;
}
}
let mut rec: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
let mut sum = Complex32::new(0.0, 0.0);
for k in 0..m {
sum = sum + u[(i, k)] * bvh[(k, j)];
}
rec[(i, j)] = sum;
}
}
for i in 0..m {
for j in 0..n {
let diff = (rec[(i, j)] - a[(i, j)]).norm();
assert!(diff < 1e-4, "Wide C32 A[{},{}] error: {}", i, j, diff);
}
}
}
#[test]
fn test_complex_gebrd_convenience_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[Complex64::new(1.0, 0.5), Complex64::new(2.0, -0.3)],
&[Complex64::new(3.0, 0.0), Complex64::new(4.0, 0.7)],
]);
let f = complex_gebrd(a.as_ref()).expect("complex_gebrd");
assert_eq!(f.d.len(), 2);
assert_eq!(f.e.len(), 1);
}
fn eye_c64(n: usize) -> Mat<Complex64> {
let mut m: Mat<Complex64> = Mat::zeros(n, n);
for i in 0..n {
m[(i, i)] = Complex64::new(1.0, 0.0);
}
m
}
fn matmul_c64(a: &Mat<Complex64>, b: &Mat<Complex64>) -> Mat<Complex64> {
assert_eq!(a.ncols(), b.nrows());
let mut c: Mat<Complex64> = Mat::zeros(a.nrows(), b.ncols());
for i in 0..a.nrows() {
for j in 0..b.ncols() {
let mut sum = Complex64::new(0.0, 0.0);
for k in 0..a.ncols() {
sum = sum + a[(i, k)] * b[(k, j)];
}
c[(i, j)] = sum;
}
}
c
}
fn conj_t_c64(a: &Mat<Complex64>) -> Mat<Complex64> {
let mut c: Mat<Complex64> = Mat::zeros(a.ncols(), a.nrows());
for i in 0..a.nrows() {
for j in 0..a.ncols() {
c[(j, i)] = a[(i, j)].conj();
}
}
c
}
fn assert_close_c64(a: &Mat<Complex64>, b: &Mat<Complex64>, tol: f64, label: &str) {
assert_eq!(a.nrows(), b.nrows(), "{label}: row mismatch");
assert_eq!(a.ncols(), b.ncols(), "{label}: col mismatch");
for i in 0..a.nrows() {
for j in 0..a.ncols() {
let diff = (a[(i, j)] - b[(i, j)]).norm();
assert!(
diff < tol,
"{label}[{i},{j}] error {diff}: got ({:.6},{:.6}) exp ({:.6},{:.6})",
a[(i, j)].re,
a[(i, j)].im,
b[(i, j)].re,
b[(i, j)].im,
);
}
}
}
fn build_bidiag_c64(d: &[f64], e: &[f64], m: usize, n: usize, upper: bool) -> Mat<Complex64> {
let mut b: Mat<Complex64> = Mat::zeros(m, n);
for (i, &di) in d.iter().enumerate() {
b[(i, i)] = Complex64::new(di, 0.0);
}
for (i, &ei) in e.iter().enumerate() {
if upper {
b[(i, i + 1)] = Complex64::new(ei, 0.0);
} else {
b[(i + 1, i)] = Complex64::new(ei, 0.0);
}
}
b
}
fn random_complex_matrix(m: usize, n: usize, seed: u64) -> Mat<Complex64> {
let mut state = seed;
let mut next = || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let x = (state >> 33) as f64 / ((1u64 << 31) as f64);
x * 2.0 - 1.0
};
let mut a: Mat<Complex64> = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
a[(i, j)] = Complex64::new(next(), next());
}
}
a
}
fn verify_ungbr_unmbr(a: &Mat<Complex64>, tol: f64, label: &str) {
use crate::svd::{Side, Trans, ungbr, unmbr};
let m = a.nrows();
let n = a.ncols();
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("compute");
let upper = m >= n;
let q = ungbr(&f, BidiagVect::Q).expect("ungbr Q");
let p = ungbr(&f, BidiagVect::P).expect("ungbr P");
assert_unitary_c64(&q, &format!("{label}/Q"), tol);
assert_unitary_c64(&p, &format!("{label}/P"), tol);
let ppt = matmul_c64(&p, &conj_t_c64(&p));
assert_close_c64(&ppt, &eye_c64(n), tol, &format!("{label}/PPH"));
let qh = conj_t_c64(&q);
let qap = matmul_c64(&matmul_c64(&qh, a), &p);
let (bm, bn) = (qap.nrows(), qap.ncols());
let b_ref = build_bidiag_c64(&f.d, &f.e, bm, bn, upper);
assert_close_c64(&qap, &b_ref, tol, &format!("{label}/QHAP"));
let qf = unmbr(
&f,
BidiagVect::Q,
Side::Left,
Trans::NoTrans,
eye_c64(m).as_ref(),
)
.expect("unmbr Q L N");
assert_unitary_c64(&qf, &format!("{label}/Qfull"), tol);
let mut qf_thin: Mat<Complex64> = Mat::zeros(m, q.ncols());
for i in 0..m {
for j in 0..q.ncols() {
qf_thin[(i, j)] = qf[(i, j)];
}
}
assert_close_c64(&qf_thin, &q, tol, &format!("{label}/Qthin-vs-apply"));
let qf_h = unmbr(
&f,
BidiagVect::Q,
Side::Left,
Trans::Trans,
eye_c64(m).as_ref(),
)
.expect("unmbr Q L C");
assert_close_c64(&qf_h, &conj_t_c64(&qf), tol, &format!("{label}/QH"));
let qf_r = unmbr(
&f,
BidiagVect::Q,
Side::Right,
Trans::NoTrans,
eye_c64(m).as_ref(),
)
.expect("unmbr Q R N");
assert_close_c64(&qf_r, &qf, tol, &format!("{label}/Q-right"));
let qf_rh = unmbr(
&f,
BidiagVect::Q,
Side::Right,
Trans::Trans,
eye_c64(m).as_ref(),
)
.expect("unmbr Q R C");
assert_close_c64(&qf_rh, &qf_h, tol, &format!("{label}/QH-right"));
let pf = unmbr(
&f,
BidiagVect::P,
Side::Left,
Trans::NoTrans,
eye_c64(n).as_ref(),
)
.expect("unmbr P L N");
assert_close_c64(&pf, &p, tol, &format!("{label}/P-vs-apply"));
let pf_h = unmbr(
&f,
BidiagVect::P,
Side::Left,
Trans::Trans,
eye_c64(n).as_ref(),
)
.expect("unmbr P L C");
assert_close_c64(&pf_h, &conj_t_c64(&pf), tol, &format!("{label}/PH"));
let pf_r = unmbr(
&f,
BidiagVect::P,
Side::Right,
Trans::NoTrans,
eye_c64(n).as_ref(),
)
.expect("unmbr P R N");
assert_close_c64(&pf_r, &pf, tol, &format!("{label}/P-right"));
let pf_rh = unmbr(
&f,
BidiagVect::P,
Side::Right,
Trans::Trans,
eye_c64(n).as_ref(),
)
.expect("unmbr P R C");
assert_close_c64(&pf_rh, &pf_h, tol, &format!("{label}/PH-right"));
let qh_a =
unmbr(&f, BidiagVect::Q, Side::Left, Trans::Trans, a.as_ref()).expect("unmbr Q^H A");
let b_full = unmbr(
&f,
BidiagVect::P,
Side::Right,
Trans::NoTrans,
qh_a.as_ref(),
)
.expect("unmbr (Q^H A) P");
let b_full_ref = build_bidiag_c64(&f.d, &f.e, m, n, upper);
assert_close_c64(&b_full, &b_full_ref, tol, &format!("{label}/QHAP-apply"));
let qb = unmbr(
&f,
BidiagVect::Q,
Side::Left,
Trans::NoTrans,
b_full.as_ref(),
)
.expect("unmbr Q B");
let a_rec = unmbr(&f, BidiagVect::P, Side::Right, Trans::Trans, qb.as_ref())
.expect("unmbr (Q B) P^H");
assert_close_c64(&a_rec, a, tol, &format!("{label}/roundtrip"));
}
#[test]
fn test_ungbr_unmbr_tall_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 2.0),
Complex64::new(3.0, -1.0),
Complex64::new(0.5, 0.5),
],
&[
Complex64::new(4.0, 0.0),
Complex64::new(5.0, 1.0),
Complex64::new(2.0, -2.0),
],
&[
Complex64::new(7.0, -1.0),
Complex64::new(8.0, 0.0),
Complex64::new(3.0, 1.0),
],
&[
Complex64::new(10.0, 0.5),
Complex64::new(11.0, -0.5),
Complex64::new(4.0, 0.0),
],
]);
verify_ungbr_unmbr(&a, 1e-9, "Tall");
}
#[test]
fn test_ungbr_unmbr_square_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 1.0),
Complex64::new(2.0, -1.0),
Complex64::new(3.0, 0.5),
],
&[
Complex64::new(4.0, 0.0),
Complex64::new(5.0, 2.0),
Complex64::new(6.0, -1.0),
],
&[
Complex64::new(7.0, -0.5),
Complex64::new(8.0, 0.0),
Complex64::new(9.0, 1.0),
],
]);
verify_ungbr_unmbr(&a, 1e-9, "Square");
}
#[test]
fn test_ungbr_unmbr_wide_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[
Complex64::new(1.0, 1.0),
Complex64::new(2.0, 0.0),
Complex64::new(3.0, -1.0),
Complex64::new(4.0, 0.5),
],
&[
Complex64::new(5.0, 0.0),
Complex64::new(6.0, 1.0),
Complex64::new(7.0, 0.0),
Complex64::new(8.0, -0.5),
],
&[
Complex64::new(0.5, -1.0),
Complex64::new(1.5, 0.5),
Complex64::new(2.5, 1.0),
Complex64::new(3.5, 0.0),
],
]);
verify_ungbr_unmbr(&a, 1e-9, "Wide");
}
#[test]
fn test_ungbr_unmbr_random_tall_c64() {
let a = random_complex_matrix(7, 5, 0x1234_5678_9abc_def0);
verify_ungbr_unmbr(&a, 1e-8, "RandomTall");
}
#[test]
fn test_ungbr_unmbr_random_wide_c64() {
let a = random_complex_matrix(5, 8, 0x0fed_cba9_8765_4321);
verify_ungbr_unmbr(&a, 1e-8, "RandomWide");
}
#[test]
fn test_ungbr_unmbr_hermitian_c64() {
let a: Mat<Complex64> = Mat::from_rows(&[
&[Complex64::new(4.0, 0.0), Complex64::new(1.0, -1.0)],
&[Complex64::new(1.0, 1.0), Complex64::new(3.0, 0.0)],
]);
verify_ungbr_unmbr(&a, 1e-10, "Hermitian");
}
#[test]
fn test_ungbr_unmbr_dimension_mismatch() {
use crate::svd::{Side, Trans, unmbr};
let a = random_complex_matrix(4, 3, 42);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("compute");
let bad: Mat<Complex64> = Mat::zeros(3, 2);
let res = unmbr(&f, BidiagVect::Q, Side::Left, Trans::NoTrans, bad.as_ref());
assert_eq!(res.err(), Some(BidiagError::DimensionMismatch));
let bad2: Mat<Complex64> = Mat::zeros(4, 2);
let res2 = unmbr(
&f,
BidiagVect::P,
Side::Right,
Trans::NoTrans,
bad2.as_ref(),
);
assert_eq!(res2.err(), Some(BidiagError::DimensionMismatch));
}
#[test]
fn test_ungbr_unmbr_c32() {
use crate::svd::{Side, Trans, ungbr, unmbr};
let a: Mat<Complex32> = Mat::from_rows(&[
&[Complex32::new(1.0, 1.0), Complex32::new(2.0, -1.0)],
&[Complex32::new(3.0, 0.0), Complex32::new(4.0, 0.5)],
&[Complex32::new(5.0, -0.5), Complex32::new(6.0, 0.0)],
]);
let f = ComplexBidiagFactors::compute(a.as_ref()).expect("compute");
let q = ungbr(&f, BidiagVect::Q).expect("ungbr Q");
let p = ungbr(&f, BidiagVect::P).expect("ungbr P");
assert_unitary_c32(&q, "Q", 1e-4);
assert_unitary_c32(&p, "P", 1e-4);
let m = 3usize;
let n = 2usize;
let mut b: Mat<Complex32> = Mat::zeros(m, n);
for i in 0..f.d.len() {
b[(i, i)] = Complex32::new(f.d[i], 0.0);
}
for i in 0..f.e.len() {
b[(i, i + 1)] = Complex32::new(f.e[i], 0.0);
}
let qb = unmbr(&f, BidiagVect::Q, Side::Left, Trans::NoTrans, b.as_ref()).expect("Q B");
let a_rec =
unmbr(&f, BidiagVect::P, Side::Right, Trans::Trans, qb.as_ref()).expect("Q B P^H");
for i in 0..m {
for j in 0..n {
let diff = (a_rec[(i, j)] - a[(i, j)]).norm();
assert!(diff < 1e-4, "C32 roundtrip A[{i},{j}] error {diff}");
}
}
}
}