use crate::{AlgorithmError, Result};
pub struct QRDecomposition {
q: Vec<Vec<f64>>,
r: Vec<Vec<f64>>,
}
impl QRDecomposition {
pub fn decompose(matrix: &[Vec<f64>]) -> Result<Self> {
if matrix.is_empty() || matrix[0].is_empty() {
return Err(AlgorithmError::InvalidInput(
"Matrix cannot be empty".to_string()
));
}
let m = matrix.len();
let n = matrix[0].len();
for row in matrix.iter() {
if row.len() != n {
return Err(AlgorithmError::InvalidInput(
"All rows must have the same length".to_string()
));
}
}
if m < n {
return Err(AlgorithmError::InvalidInput(
"Matrix must have at least as many rows as columns".to_string()
));
}
let mut r = matrix.to_vec();
let mut q = identity_matrix(m);
for k in 0..n.min(m - 1) {
let h = householder_matrix(&r, k, m)?;
r = matrix_multiply(&h, &r)?;
q = matrix_multiply(&h, &q)?;
}
q = transpose(&q);
Ok(QRDecomposition { q, r })
}
pub fn q(&self) -> &[Vec<f64>] {
&self.q
}
pub fn r(&self) -> &[Vec<f64>] {
&self.r
}
pub fn solve(&self, b: &[f64]) -> Result<Vec<f64>> {
if b.len() != self.q.len() {
return Err(AlgorithmError::DimensionMismatch(
format!("Vector length {} doesn't match matrix dimension {}",
b.len(), self.q.len())
));
}
let qt_b = matrix_vector_multiply(&transpose(&self.q), b)?;
back_substitution(&self.r, &qt_b)
}
}
fn householder_matrix(a: &[Vec<f64>], k: usize, m: usize) -> Result<Vec<Vec<f64>>> {
let _n = a[0].len();
let mut x: Vec<f64> = (k..m).map(|i| a[i][k]).collect();
let norm_x: f64 = x.iter().map(|&val| val * val).sum::<f64>().sqrt();
if norm_x < 1e-10 {
return Ok(identity_matrix(m));
}
x[0] += if x[0] >= 0.0 { norm_x } else { -norm_x };
let norm_v: f64 = x.iter().map(|&val| val * val).sum::<f64>().sqrt();
if norm_v < 1e-10 {
return Ok(identity_matrix(m));
}
for val in x.iter_mut() {
*val /= norm_v;
}
let mut h = identity_matrix(m);
for i in 0..(m - k) {
for j in 0..(m - k) {
h[i + k][j + k] -= 2.0 * x[i] * x[j];
}
}
Ok(h)
}
fn identity_matrix(n: usize) -> Vec<Vec<f64>> {
let mut matrix = vec![vec![0.0; n]; n];
for (i, row) in matrix.iter_mut().enumerate() {
row[i] = 1.0;
}
matrix
}
fn matrix_multiply(a: &[Vec<f64>], b: &[Vec<f64>]) -> Result<Vec<Vec<f64>>> {
let m = a.len();
let n = b[0].len();
let p = a[0].len();
if p != b.len() {
return Err(AlgorithmError::DimensionMismatch(
"Matrix dimensions incompatible for multiplication".to_string()
));
}
let mut result = vec![vec![0.0; n]; m];
for i in 0..m {
for j in 0..n {
for k in 0..p {
result[i][j] += a[i][k] * b[k][j];
}
}
}
Ok(result)
}
fn matrix_vector_multiply(a: &[Vec<f64>], b: &[f64]) -> Result<Vec<f64>> {
let m = a.len();
let n = a[0].len();
if n != b.len() {
return Err(AlgorithmError::DimensionMismatch(
"Matrix and vector dimensions incompatible".to_string()
));
}
let mut result = vec![0.0; m];
for i in 0..m {
for j in 0..n {
result[i] += a[i][j] * b[j];
}
}
Ok(result)
}
fn transpose(matrix: &[Vec<f64>]) -> Vec<Vec<f64>> {
let m = matrix.len();
let n = matrix[0].len();
let mut result = vec![vec![0.0; m]; n];
for i in 0..m {
for j in 0..n {
result[j][i] = matrix[i][j];
}
}
result
}
fn back_substitution(r: &[Vec<f64>], b: &[f64]) -> Result<Vec<f64>> {
let n = r[0].len();
let mut x = vec![0.0; n];
for i in (0..n).rev() {
if r[i][i].abs() < 1e-10 {
return Err(AlgorithmError::NumericalInstability(
"Matrix is singular or nearly singular".to_string()
));
}
let mut sum = b[i];
for j in (i + 1)..n {
sum -= r[i][j] * x[j];
}
x[i] = sum / r[i][i];
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qr_decomposition() {
let matrix = vec![
vec![12.0, -51.0, 4.0],
vec![6.0, 167.0, -68.0],
vec![-4.0, 24.0, -41.0],
];
let qr = QRDecomposition::decompose(&matrix).unwrap();
let q = qr.q();
let r = qr.r();
let qt = transpose(q);
let qtq = matrix_multiply(&qt, q).unwrap();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((qtq[i][j] - expected).abs() < 1e-10);
}
}
for i in 0..3 {
for j in 0..i {
assert!(r[i][j].abs() < 1e-10);
}
}
}
#[test]
fn test_solve() {
let matrix = vec![
vec![1.0, 2.0],
vec![3.0, 4.0],
vec![5.0, 6.0],
];
let b = vec![1.0, 2.0, 3.0];
let qr = QRDecomposition::decompose(&matrix).unwrap();
let x = qr.solve(&b).unwrap();
assert_eq!(x.len(), 2);
}
}