pub struct QrResult {
data: Vec<f64>,
tau: Vec<f64>,
perm: Vec<usize>,
m: usize,
n: usize,
}
impl QrResult {
#[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
pub fn factorize(data: &mut [f64], m: usize, n: usize) -> Self {
debug_assert!(
data.len() >= m * n,
"data length {} < m*n = {}",
data.len(),
m * n
);
let k = m.min(n);
let mut tau = vec![0.0; k];
let mut perm: Vec<usize> = (0..n).collect();
let mut col_norms = vec![0.0; n];
for j in 0..n {
let mut s = 0.0;
for i in 0..m {
let v = data[i * n + j];
s += v * v;
}
col_norms[j] = s;
}
for step in 0..k {
let mut best_col = step;
let mut best_norm = col_norms[step];
for j in (step + 1)..n {
if col_norms[j] > best_norm {
best_norm = col_norms[j];
best_col = j;
}
}
if best_col != step {
for i in 0..m {
data.swap(i * n + step, i * n + best_col);
}
col_norms.swap(step, best_col);
perm.swap(step, best_col);
}
let mut norm_sq = 0.0;
for i in step..m {
let v = data[i * n + step];
norm_sq += v * v;
}
if norm_sq < 1e-300 {
tau[step] = 0.0;
continue;
}
let norm = norm_sq.sqrt();
let alpha = data[step * n + step];
let beta = if alpha >= 0.0 { -norm } else { norm };
tau[step] = (beta - alpha) / beta;
let scale = 1.0 / (alpha - beta);
for i in (step + 1)..m {
data[i * n + step] *= scale;
}
data[step * n + step] = beta;
for j in (step + 1)..n {
let mut dot = data[step * n + j];
for i in (step + 1)..m {
dot += data[i * n + step] * data[i * n + j];
}
let t = tau[step] * dot;
data[step * n + j] -= t;
for i in (step + 1)..m {
data[i * n + j] -= data[i * n + step] * t;
}
}
let recompute_interval = (k / 4).max(1);
let needs_recompute = (step + 1) % recompute_interval == 0;
for j in (step + 1)..n {
if needs_recompute {
let mut s = 0.0;
for i in (step + 1)..m {
let v = data[i * n + j];
s += v * v;
}
col_norms[j] = s;
} else {
let v = data[step * n + j];
col_norms[j] -= v * v;
if col_norms[j] < 0.0 {
col_norms[j] = 0.0;
}
}
}
}
Self {
data: data.to_vec(),
tau,
perm,
m,
n,
}
}
#[must_use]
pub fn rank(&self, tol: f64) -> usize {
let k = self.m.min(self.n);
if k == 0 {
return 0;
}
let r00 = self.data[0].abs();
if r00 < 1e-300 {
return 0;
}
let threshold = tol * r00;
let mut rank = 0;
for i in 0..k {
if self.data[i * self.n + i].abs() > threshold {
rank += 1;
} else {
break;
}
}
rank
}
pub fn qt_mul(&self, b: &[f64]) -> Vec<f64> {
let mut result = b.to_vec();
let k = self.m.min(self.n);
for step in 0..k {
if self.tau[step].abs() < 1e-300 {
continue;
}
let mut dot = result[step];
for i in (step + 1)..self.m {
dot += self.data[i * self.n + step] * result[i];
}
let t = self.tau[step] * dot;
result[step] -= t;
for i in (step + 1)..self.m {
result[i] -= self.data[i * self.n + step] * t;
}
}
result
}
pub fn solve_least_squares(&self, b: &[f64]) -> Vec<f64> {
let qtb = self.qt_mul(b);
let k = self.m.min(self.n);
let mut z = vec![0.0; self.n];
for i in (0..k).rev() {
let rii = self.data[i * self.n + i];
if rii.abs() < 1e-300 {
continue;
}
let mut s = qtb[i];
for j in (i + 1)..k.min(self.n) {
s -= self.data[i * self.n + j] * z[j];
}
z[i] = s / rii;
}
let mut x = vec![0.0; self.n];
for (i, &pi) in self.perm.iter().enumerate() {
x[pi] = z[i];
}
x
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn identity_3x3() {
let mut data = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let qr = QrResult::factorize(&mut data, 3, 3);
assert_eq!(qr.rank(1e-10), 3);
}
#[test]
fn known_3x3() {
let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0];
let qr = QrResult::factorize(&mut data, 3, 3);
assert_eq!(qr.rank(1e-10), 3);
let x = qr.solve_least_squares(&[1.0, 1.0, 1.0]);
let a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0];
for i in 0..3 {
let row_sum = a[i * 3] * x[0] + a[i * 3 + 1] * x[1] + a[i * 3 + 2] * x[2];
assert!((row_sum - 1.0).abs() < 1e-10, "row {i}: {row_sum} != 1.0");
}
}
#[test]
fn rank_deficient() {
let mut data = vec![1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0];
let qr = QrResult::factorize(&mut data, 2, 4);
assert_eq!(qr.rank(1e-10), 2);
}
#[test]
fn overdetermined_least_squares() {
let mut data = vec![1.0, 1.0, 1.0, -1.0, 1.0, 0.0];
let qr = QrResult::factorize(&mut data, 3, 2);
let x = qr.solve_least_squares(&[1.0, 0.0, 0.5]);
assert!((x[0] - 0.5).abs() < 1e-10, "x[0] = {}", x[0]);
assert!((x[1] - 0.5).abs() < 1e-10, "x[1] = {}", x[1]);
}
#[test]
fn empty_matrix() {
let mut data = vec![];
let qr = QrResult::factorize(&mut data, 0, 0);
assert_eq!(qr.rank(1e-10), 0);
}
#[test]
fn single_element() {
let mut data = vec![5.0];
let qr = QrResult::factorize(&mut data, 1, 1);
assert_eq!(qr.rank(1e-10), 1);
let x = qr.solve_least_squares(&[10.0]);
assert!((x[0] - 2.0).abs() < 1e-10);
}
}