use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_qr_square() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 10.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..3 {
for j in 0..3 {
let mut sum = 0.0;
for k in 0..3 {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(sum, expected, 1e-10),
"Q^T*Q[{},{}] = {}, expected {}",
i,
j,
sum,
expected
);
}
}
assert!(approx_eq(r[(1, 0)], 0.0, 1e-10));
assert!(approx_eq(r[(2, 0)], 0.0, 1e-10));
assert!(approx_eq(r[(2, 1)], 0.0, 1e-10));
for i in 0..3 {
for j in 0..3 {
let mut sum = 0.0;
for k in 0..3 {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
approx_eq(sum, a[(i, j)], 1e-10),
"QR[{},{}] = {}, A = {}",
i,
j,
sum,
a[(i, j)]
);
}
}
}
#[test]
fn test_qr_tall() {
let a = Mat::from_rows(&[&[1.0f64, 2.0], &[3.0, 4.0], &[5.0, 6.0], &[7.0, 8.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let q = qr.q();
let r = qr.r();
assert_eq!(q.nrows(), 4);
assert_eq!(q.ncols(), 4);
assert_eq!(r.nrows(), 4);
assert_eq!(r.ncols(), 2);
for i in 0..4 {
for j in 0..4 {
let mut sum = 0.0;
for k in 0..4 {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(approx_eq(sum, expected, 1e-10));
}
}
for i in 0..4 {
for j in 0..2 {
let mut sum = 0.0;
for k in 0..4 {
sum += q[(i, k)] * r[(k, j)];
}
assert!(approx_eq(sum, a[(i, j)], 1e-10));
}
}
}
#[test]
fn test_qr_wide() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let q = qr.q();
let r = qr.r();
assert_eq!(q.nrows(), 2);
assert_eq!(q.ncols(), 2);
assert_eq!(r.nrows(), 2);
assert_eq!(r.ncols(), 3);
for i in 0..2 {
for j in 0..3 {
let mut sum = 0.0;
for k in 0..2 {
sum += q[(i, k)] * r[(k, j)];
}
assert!(approx_eq(sum, a[(i, j)], 1e-10));
}
}
}
#[test]
fn test_qr_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 qr = Qr::compute(eye.as_ref()).expect("QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..3 {
for j in 0..3 {
if i == j {
assert!(q[(i, j)].abs() > 0.99);
assert!(r[(i, j)].abs() > 0.99);
} else {
assert!(approx_eq(q[(i, j)], 0.0, 1e-10));
assert!(approx_eq(r[(i, j)], 0.0, 1e-10));
}
}
}
}
#[test]
fn test_qr_thin() {
let a = Mat::from_rows(&[&[1.0f64, 2.0], &[3.0, 4.0], &[5.0, 6.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let q_thin = qr.q_thin();
let r_thin = qr.r_thin();
assert_eq!(q_thin.nrows(), 3);
assert_eq!(q_thin.ncols(), 2);
assert_eq!(r_thin.nrows(), 2);
assert_eq!(r_thin.ncols(), 2);
for i in 0..3 {
for j in 0..2 {
let mut sum = 0.0;
for k in 0..2 {
sum += q_thin[(i, k)] * r_thin[(k, j)];
}
assert!(approx_eq(sum, a[(i, j)], 1e-10));
}
}
}
#[test]
fn test_qr_least_squares() {
let a = Mat::from_rows(&[&[1.0f64, 1.0], &[1.0, 2.0], &[1.0, 3.0]]);
let b = Mat::from_rows(&[&[1.0f64], &[2.0], &[2.5]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let x = qr
.solve_least_squares(b.as_ref())
.expect("least squares should succeed");
assert!(x.nrows() == 2);
assert!(x.ncols() == 1);
let mut ax = [0.0; 3];
for i in 0..3 {
for j in 0..2 {
ax[i] += a[(i, j)] * x[(j, 0)];
}
}
let mut residual = 0.0;
for i in 0..3 {
residual += (ax[i] - b[(i, 0)]).powi(2);
}
residual = residual.sqrt();
assert!(residual < 0.5); }
#[test]
fn test_qr_solve_least_squares_dimension_mismatch() {
let a = Mat::from_rows(&[&[1.0f64, 2.0], &[3.0, 4.0], &[5.0, 6.0]]);
let b = Mat::from_rows(&[&[1.0f64], &[2.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let result = qr.solve_least_squares(b.as_ref());
match result {
Err(QrError::DimensionMismatch { expected, actual }) => {
assert_eq!(expected, 3);
assert_eq!(actual, 2);
}
other => panic!("expected QrError::DimensionMismatch, got {other:?}"),
}
}
#[test]
fn test_qr_solve_least_squares_near_singular_reports_error() {
let a = Mat::from_rows(&[&[1.0f64, 1.0], &[0.0, 1.0e-15]]);
let b = Mat::from_rows(&[&[1.0f64], &[1.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let r = qr.r();
assert!(
r[(1, 1)] != 0.0,
"R[1,1] should be nonzero (exact zero would take a different code path)"
);
assert!(
r[(1, 1)].abs() < 1e-12,
"R[1,1] should be tiny, got {}",
r[(1, 1)]
);
let result = qr.solve_least_squares(b.as_ref());
match result {
Err(QrError::NearlySingular { index }) => {
assert_eq!(index, 1, "near-singularity should be reported at index 1");
}
other => panic!("expected QrError::NearlySingular, got {other:?}"),
}
}
#[test]
fn test_qr_extreme_magnitude_column_stays_orthogonal() {
let huge = 1.0e200_f64;
let a = Mat::from_rows(&[&[huge, 2.0, 3.0], &[huge, 5.0, 6.0], &[huge, 8.0, 10.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed even with extreme magnitudes");
let q = qr.q();
for i in 0..3 {
for j in 0..3 {
assert!(
q[(i, j)].is_finite(),
"Q[{i},{j}] = {} is not finite",
q[(i, j)]
);
}
}
for i in 0..3 {
for j in 0..3 {
let mut sum = 0.0;
for k in 0..3 {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
approx_eq(sum, expected, 1e-8),
"Q^T*Q[{i},{j}] = {sum}, expected {expected}"
);
}
}
let r = qr.r();
for i in 0..3 {
for j in 0..3 {
let mut sum = 0.0;
for k in 0..3 {
sum += q[(i, k)] * r[(k, j)];
}
let rel_tol = a[(i, j)].abs() * 1e-8;
assert!(
(sum - a[(i, j)]).abs() <= rel_tol,
"QR[{i},{j}] = {sum}, A = {}",
a[(i, j)]
);
}
}
}
#[test]
fn test_qr_f32() {
let a = Mat::from_rows(&[&[1.0f32, 2.0], &[3.0, 4.0]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..2 {
for j in 0..2 {
let mut sum: f32 = 0.0;
for k in 0..2 {
sum += q[(i, k)] * r[(k, j)];
}
assert!((sum - a[(i, j)]).abs() < 1e-5);
}
}
}
#[test]
fn test_qr_single() {
let a = Mat::from_rows(&[&[3.0f64]]);
let qr = Qr::compute(a.as_ref()).expect("QR should succeed");
let q = qr.q();
let r = qr.r();
assert!(q[(0, 0)].abs() > 0.99);
assert!(r[(0, 0)].abs() > 2.99);
assert!(approx_eq((q[(0, 0)] * r[(0, 0)]).abs(), 3.0, 1e-10));
}
#[test]
fn test_qr_blocked_vs_unblocked_4x4() {
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 qr_blocked = Qr::compute_blocked(a.as_ref(), 2).expect("blocked QR should succeed");
let q_b = qr_blocked.q();
let r_b = qr_blocked.r();
for i in 0..4 {
for j in 0..4 {
let mut sum = 0.0;
for k in 0..4 {
sum += q_b[(i, k)] * r_b[(k, j)];
}
let diff = sum - a[(i, j)];
assert!(
diff.abs() < 1e-10,
"Blocked reconstruction error at ({}, {}): got {}, expected {}, diff={}",
i,
j,
sum,
a[(i, j)],
diff
);
}
}
for i in 0..4 {
for j in 0..4 {
let mut sum = 0.0;
for k in 0..4 {
sum += q_b[(k, i)] * q_b[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-10,
"Q not orthogonal at ({}, {}): got {}, expected {}",
i,
j,
sum,
expected
);
}
}
for i in 0..4 {
for j in 0..i {
assert!(
r_b[(i, j)].abs() < 1e-10,
"R not upper triangular at ({}, {}): got {}",
i,
j,
r_b[(i, j)]
);
}
}
}
#[test]
fn test_qr_blocked_various_block_sizes() {
let n = 12;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i * 3 + j * 7 + 1) % 11) as f64 + 1.0;
}
a[(i, i)] += 20.0; }
for nb in [1, 2, 3, 4, 6, 12] {
let qr = Qr::compute_blocked(a.as_ref(), nb).expect("blocked QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-9,
"nb={}: reconstruction error at ({}, {}): diff={}",
nb,
i,
j,
sum - a[(i, j)]
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-9,
"nb={}: Q not orthogonal at ({}, {})",
nb,
i,
j
);
}
}
}
}
#[test]
fn test_qr_blocked_small() {
let n = 8;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i + j) % 5 + 1) as f64;
}
}
let qr_blocked = Qr::compute_blocked(a.as_ref(), 4).expect("blocked QR should succeed");
let q = qr_blocked.q();
let r = qr_blocked.r();
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
let diff = sum - a[(i, j)];
assert!(
diff.abs() < 1e-10,
"Reconstruction error at ({}, {}): got {}, expected {}, diff={}",
i,
j,
sum,
a[(i, j)],
diff
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-10,
"Q not orthogonal at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_correctness() {
let n = 200;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i + j) % 10 + 1) as f64;
}
a[(i, i)] += 10.0; }
let qr_blocked = Qr::compute_blocked(a.as_ref(), 64).expect("blocked QR should succeed");
let q = qr_blocked.q();
let r = qr_blocked.r();
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-9,
"Q not orthogonal at ({}, {}): got {}, expected {}",
i,
j,
sum,
expected
);
}
}
for i in 0..n {
for j in 0..i {
assert!(
r[(i, j)].abs() < 1e-10,
"R not upper triangular at ({}, {}): got {}",
i,
j,
r[(i, j)]
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-8,
"Reconstruction error at ({}, {}): got {}, expected {}",
i,
j,
sum,
a[(i, j)]
);
}
}
}
#[test]
fn test_qr_auto_selection() {
let n = 150;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i * 7 + j * 11) % 13 + 1) as f64;
}
}
let qr = Qr::compute_auto(a.as_ref()).expect("auto QR should succeed");
let q = qr.q();
let r = qr.r();
let tol = 1e-5;
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < tol,
"Q not orthogonal at ({}, {}): got {}, expected {}, diff={}",
i,
j,
sum,
expected,
(sum - expected).abs()
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-8,
"QR reconstruction error at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_tall_matrix() {
let m = 300;
let n = 100;
let mut a = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
a[(i, j)] = ((i + 2 * j) % 7 + 1) as f64;
}
}
let qr = Qr::compute_blocked(a.as_ref(), 32).expect("blocked QR should succeed");
let r = qr.r_thin();
for i in 0..n {
for j in 0..i {
assert!(
r[(i, j)].abs() < 1e-10,
"R not upper triangular at ({}, {}): got {}",
i,
j,
r[(i, j)]
);
}
}
let q_thin = qr.q_thin();
for i in 0..m {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q_thin[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-8,
"Thin QR reconstruction error at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_wide_matrix() {
let m = 50;
let n = 120;
let mut a = Mat::zeros(m, n);
for i in 0..m {
for j in 0..n {
a[(i, j)] = ((i * 5 + j * 3 + 2) % 11) as f64 + 0.5;
}
}
let qr = Qr::compute_blocked(a.as_ref(), 16).expect("blocked QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..m {
for j in 0..m {
let mut sum = 0.0;
for k in 0..m {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-9,
"Q not orthogonal at ({}, {}): diff={}",
i,
j,
(sum - expected).abs()
);
}
}
let k = m.min(n);
for i in 0..m {
for j in 0..i.min(k) {
assert!(
r[(i, j)].abs() < 1e-10,
"R not upper triangular at ({}, {})",
i,
j
);
}
}
for i in 0..m {
for j in 0..n {
let mut sum = 0.0;
for k in 0..m {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-8,
"Wide reconstruction error at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_f32() {
let n = 32;
let mut a: Mat<f32> = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i * 3 + j * 5 + 1) % 9 + 1) as f32;
}
a[(i, i)] += 10.0;
}
let qr = Qr::compute_blocked(a.as_ref(), 8).expect("f32 blocked QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..n {
for j in 0..n {
let mut sum: f32 = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-3,
"f32 blocked reconstruction error at ({}, {}): diff={}",
i,
j,
(sum - a[(i, j)]).abs()
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum: f32 = 0.0;
for k in 0..n {
sum += q[(k, i)] * q[(k, j)];
}
let expected: f32 = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-3,
"f32 Q not orthogonal at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_identity_matrix() {
let n = 16;
let mut eye = Mat::zeros(n, n);
for i in 0..n {
eye[(i, i)] = 1.0f64;
}
let qr = Qr::compute_blocked(eye.as_ref(), 4).expect("identity blocked QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..n {
for j in 0..n {
let expected: f64 = if i == j { 1.0 } else { 0.0 };
assert!(
(q[(i, j)].abs() - expected.abs()).abs() < 1e-10
|| (i == j && q[(i, j)].abs() > 0.99),
"Identity Q error at ({}, {})",
i,
j
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-10,
"Identity reconstruction error at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_block_size_1() {
let n = 20;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i + j + 1) % 7) as f64 + 1.0;
}
a[(i, i)] += 15.0;
}
let qr_unblocked = Qr::compute(a.as_ref()).expect("unblocked QR should succeed");
let qr_blocked = Qr::compute_blocked(a.as_ref(), 1).expect("nb=1 blocked QR should succeed");
let r_u = qr_unblocked.r();
let r_b = qr_blocked.r();
for i in 0..n {
for j in 0..n {
assert!(
(r_u[(i, j)] - r_b[(i, j)]).abs() < 1e-10,
"R mismatch at ({}, {}): unblocked={}, blocked={}",
i,
j,
r_u[(i, j)],
r_b[(i, j)]
);
}
}
}
#[test]
fn test_qr_blocked_block_size_exceeds_n() {
let n = 8;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = (i * n + j + 1) as f64;
}
}
let qr = Qr::compute_blocked(a.as_ref(), 64).expect("large nb should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-9,
"Large nb reconstruction error at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_auto_small_uses_unblocked() {
let n = 64;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = ((i * 3 + j * 7) % 17 + 1) as f64;
}
a[(i, i)] += 20.0;
}
let qr = Qr::compute_auto(a.as_ref()).expect("auto QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-8,
"Auto small reconstruction error at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_blocked_well_conditioned() {
let n = 100;
let mut a = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
a[(i, j)] = if i == j {
100.0
} else {
1.0 / ((i as f64 - j as f64).abs() + 1.0)
};
}
}
let qr = Qr::compute_blocked(a.as_ref(), 32).expect("well-conditioned QR should succeed");
let q = qr.q();
let r = qr.r();
for i in 0..n {
for j in i..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(k, i)] * q[(k, j)];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sum - expected).abs() < 1e-10,
"Well-conditioned Q orthogonality error at ({}, {}): diff={}",
i,
j,
(sum - expected).abs()
);
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += q[(i, k)] * r[(k, j)];
}
assert!(
(sum - a[(i, j)]).abs() < 1e-9,
"Well-conditioned reconstruction error at ({}, {})",
i,
j
);
}
}
}