use super::DimensionMismatch;
use crate::scalar::Scalar;
use crate::storage::Storage;
pub fn qr_householder<S, T>(
a: &S,
rows: usize,
cols: usize,
out_q: &mut [T],
out_r: &mut [T],
scratch: &mut [T],
) -> Result<(), DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialOrd,
{
if rows < cols {
return Err(DimensionMismatch);
}
if a.len() != rows * cols
|| out_r.len() != rows * cols
|| out_q.len() != rows * rows
|| scratch.len() != rows
{
return Err(DimensionMismatch);
}
for (i, slot) in out_r.iter_mut().enumerate() {
let Some(&x) = a.get(i) else {
return Err(DimensionMismatch);
};
*slot = x;
}
let zero = T::zero();
let one = T::one();
let two = one.add(one);
for slot in out_q.iter_mut() {
*slot = zero;
}
for i in 0..rows {
out_q[i * rows + i] = one;
}
for k in 0..cols {
let mut norm_sq = zero;
for i in k..rows {
let xi = out_r[i * cols + k];
norm_sq = norm_sq.add(xi.mul(xi));
}
let norm = norm_sq.sqrt();
if norm == zero {
continue;
}
let x1 = out_r[k * cols + k];
let alpha = if x1 < zero { zero.sub(norm) } else { norm };
scratch[k] = x1.add(alpha);
for i in (k + 1)..rows {
scratch[i] = out_r[i * cols + k];
}
let mut v_dot_v = zero;
for &v_i in &scratch[k..rows] {
v_dot_v = v_dot_v.add(v_i.mul(v_i));
}
if v_dot_v == zero {
continue;
}
for c in k..cols {
let mut dot = zero;
for i in k..rows {
dot = dot.add(scratch[i].mul(out_r[i * cols + c]));
}
let factor = two.mul(dot).div(v_dot_v);
for i in k..rows {
out_r[i * cols + c] = out_r[i * cols + c].sub(factor.mul(scratch[i]));
}
}
for i in 0..rows {
let mut dot = zero;
for j in k..rows {
dot = dot.add(scratch[j].mul(out_q[i * rows + j]));
}
let factor = two.mul(dot).div(v_dot_v);
for j in k..rows {
out_q[i * rows + j] = out_q[i * rows + j].sub(factor.mul(scratch[j]));
}
}
}
Ok(())
}
pub fn qr_gram_schmidt<S, T>(
a: &S,
rows: usize,
cols: usize,
out_q: &mut [T],
out_r: &mut [T],
scratch: &mut [T],
) -> Result<(), DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialEq,
{
if rows < cols {
return Err(DimensionMismatch);
}
if a.len() != rows * cols
|| out_q.len() != rows * cols
|| out_r.len() != cols * cols
|| scratch.len() != rows
{
return Err(DimensionMismatch);
}
let zero = T::zero();
for slot in out_r.iter_mut() {
*slot = zero;
}
for j in 0..cols {
for (r, slot) in scratch.iter_mut().enumerate() {
let Some(&x) = a.get(r * cols + j) else {
return Err(DimensionMismatch);
};
*slot = x;
}
for i in 0..j {
let mut dot = zero;
for r in 0..rows {
dot = dot.add(out_q[r * cols + i].mul(scratch[r]));
}
out_r[i * cols + j] = dot;
for r in 0..rows {
scratch[r] = scratch[r].sub(dot.mul(out_q[r * cols + i]));
}
}
let mut norm_sq = zero;
for &v_r in scratch.iter() {
norm_sq = norm_sq.add(v_r.mul(v_r));
}
let norm = norm_sq.sqrt();
out_r[j * cols + j] = norm;
if norm == zero {
for r in 0..rows {
out_q[r * cols + j] = zero;
}
continue;
}
for r in 0..rows {
out_q[r * cols + j] = scratch[r].div(norm);
}
}
Ok(())
}
pub fn qr<S, T>(
a: &S,
rows: usize,
cols: usize,
out_q: &mut [T],
out_r: &mut [T],
scratch: &mut [T],
) -> Result<(), DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialOrd,
{
qr_householder(a, rows, cols, out_q, out_r, scratch)
}
#[cfg(test)]
mod tests {
use super::{DimensionMismatch, qr, qr_gram_schmidt, qr_householder};
use crate::algorithm::matrix::{mul_matrix, transpose};
use crate::storage::StaticStorage;
#[test]
fn qr_householder_of_square_matrix_q_is_orthogonal() {
let a = StaticStorage::new([3.0_f64, 5.0, 4.0, 0.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 4];
let mut scratch = [0.0; 2];
assert_eq!(
qr_householder(&a, 2, 2, &mut q, &mut r, &mut scratch),
Ok(())
);
let mut q_t = [0.0; 4];
transpose(&StaticStorage::new(q), 2, 2, &mut q_t).unwrap();
let mut q_t_q = [0.0; 4];
mul_matrix(
&StaticStorage::new(q_t),
2,
2,
&StaticStorage::new(q),
2,
2,
&mut q_t_q,
)
.unwrap();
for (actual, expected) in q_t_q.iter().zip([1.0, 0.0, 0.0, 1.0]) {
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_householder_of_square_matrix_q_times_r_reconstructs_a() {
let a = StaticStorage::new([3.0_f64, 5.0, 4.0, 0.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 4];
let mut scratch = [0.0; 2];
assert_eq!(
qr_householder(&a, 2, 2, &mut q, &mut r, &mut scratch),
Ok(())
);
let mut qr = [0.0; 4];
mul_matrix(
&StaticStorage::new(q),
2,
2,
&StaticStorage::new(r),
2,
2,
&mut qr,
)
.unwrap();
for (actual, expected) in qr.iter().zip([3.0, 5.0, 4.0, 0.0]) {
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_householder_of_non_square_matrix_with_more_rows_than_columns() {
let a = StaticStorage::new([12.0_f64, 1.0, 6.0, 2.0, -4.0, 3.0]);
let mut q = [0.0; 9];
let mut r = [0.0; 6];
let mut scratch = [0.0; 3];
assert_eq!(
qr_householder(&a, 3, 2, &mut q, &mut r, &mut scratch),
Ok(())
);
let mut q_t = [0.0; 9];
transpose(&StaticStorage::new(q), 3, 3, &mut q_t).unwrap();
let mut q_t_q = [0.0; 9];
mul_matrix(
&StaticStorage::new(q_t),
3,
3,
&StaticStorage::new(q),
3,
3,
&mut q_t_q,
)
.unwrap();
#[rustfmt::skip]
let identity = [
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
];
for (actual, expected) in q_t_q.iter().zip(identity) {
assert!((actual - expected).abs() < 1e-9);
}
let mut qr = [0.0; 6];
mul_matrix(
&StaticStorage::new(q),
3,
3,
&StaticStorage::new(r),
3,
2,
&mut qr,
)
.unwrap();
let expected_a = [12.0, 1.0, 6.0, 2.0, -4.0, 3.0];
for (actual, expected) in qr.iter().zip(expected_a) {
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_householder_of_matrix_with_more_columns_than_rows_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 6];
let mut scratch = [0.0; 2];
assert_eq!(
qr_householder(&a, 2, 3, &mut q, &mut r, &mut scratch),
Err(DimensionMismatch)
);
}
#[test]
fn qr_householder_mismatched_scratch_length_is_an_error_not_a_panic() {
let a = StaticStorage::new([3.0, 5.0, 4.0, 0.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 4];
let mut scratch = [0.0; 3];
assert_eq!(
qr_householder(&a, 2, 2, &mut q, &mut r, &mut scratch),
Err(DimensionMismatch)
);
}
#[test]
fn qr_gram_schmidt_of_square_matrix_q_is_orthogonal() {
let a = StaticStorage::new([3.0_f64, 5.0, 4.0, 0.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 4];
let mut scratch = [0.0; 2];
assert_eq!(
qr_gram_schmidt(&a, 2, 2, &mut q, &mut r, &mut scratch),
Ok(())
);
let mut q_t = [0.0; 4];
transpose(&StaticStorage::new(q), 2, 2, &mut q_t).unwrap();
let mut q_t_q = [0.0; 4];
mul_matrix(
&StaticStorage::new(q_t),
2,
2,
&StaticStorage::new(q),
2,
2,
&mut q_t_q,
)
.unwrap();
for (actual, expected) in q_t_q.iter().zip([1.0, 0.0, 0.0, 1.0]) {
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_gram_schmidt_of_square_matrix_q_times_r_reconstructs_a() {
let a = StaticStorage::new([3.0_f64, 5.0, 4.0, 0.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 4];
let mut scratch = [0.0; 2];
assert_eq!(
qr_gram_schmidt(&a, 2, 2, &mut q, &mut r, &mut scratch),
Ok(())
);
let mut qr_product = [0.0; 4];
mul_matrix(
&StaticStorage::new(q),
2,
2,
&StaticStorage::new(r),
2,
2,
&mut qr_product,
)
.unwrap();
for (actual, expected) in qr_product.iter().zip([3.0, 5.0, 4.0, 0.0]) {
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_gram_schmidt_of_non_square_matrix_with_more_rows_than_columns() {
let a = StaticStorage::new([12.0_f64, 1.0, 6.0, 2.0, -4.0, 3.0]);
let mut q = [0.0; 6];
let mut r = [0.0; 4];
let mut scratch = [0.0; 3];
assert_eq!(
qr_gram_schmidt(&a, 3, 2, &mut q, &mut r, &mut scratch),
Ok(())
);
let mut q_t = [0.0; 6];
transpose(&StaticStorage::new(q), 3, 2, &mut q_t).unwrap();
let mut q_t_q = [0.0; 4];
mul_matrix(
&StaticStorage::new(q_t),
2,
3,
&StaticStorage::new(q),
3,
2,
&mut q_t_q,
)
.unwrap();
for (actual, expected) in q_t_q.iter().zip([1.0, 0.0, 0.0, 1.0]) {
assert!((actual - expected).abs() < 1e-9);
}
let mut qr_product = [0.0; 6];
mul_matrix(
&StaticStorage::new(q),
3,
2,
&StaticStorage::new(r),
2,
2,
&mut qr_product,
)
.unwrap();
let expected_a = [12.0, 1.0, 6.0, 2.0, -4.0, 3.0];
for (actual, expected) in qr_product.iter().zip(expected_a) {
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_gram_schmidt_of_matrix_with_more_columns_than_rows_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 9];
let mut scratch = [0.0; 2];
assert_eq!(
qr_gram_schmidt(&a, 2, 3, &mut q, &mut r, &mut scratch),
Err(DimensionMismatch)
);
}
#[test]
fn qr_gram_schmidt_mismatched_scratch_length_is_an_error_not_a_panic() {
let a = StaticStorage::new([3.0, 5.0, 4.0, 0.0]);
let mut q = [0.0; 4];
let mut r = [0.0; 4];
let mut scratch = [0.0; 3];
assert_eq!(
qr_gram_schmidt(&a, 2, 2, &mut q, &mut r, &mut scratch),
Err(DimensionMismatch)
);
}
#[test]
fn qr_matches_qr_householder() {
let a = StaticStorage::new([3.0_f64, 5.0, 4.0, 0.0]);
let mut q_high_level = [0.0; 4];
let mut r_high_level = [0.0; 4];
let mut scratch_high_level = [0.0; 2];
assert_eq!(
qr(
&a,
2,
2,
&mut q_high_level,
&mut r_high_level,
&mut scratch_high_level
),
Ok(())
);
let mut q_explicit = [0.0; 4];
let mut r_explicit = [0.0; 4];
let mut scratch_explicit = [0.0; 2];
assert_eq!(
qr_householder(
&a,
2,
2,
&mut q_explicit,
&mut r_explicit,
&mut scratch_explicit
),
Ok(())
);
assert_eq!(q_high_level, q_explicit);
assert_eq!(r_high_level, r_explicit);
}
}