mathhook_core/matrices/decomposition/qr.rs
1//! QR decomposition algorithms
2//!
3//! This module provides QR decomposition using the Gram-Schmidt process
4//! for orthogonalization and solving least squares problems.
5
6use crate::core::Expression;
7use crate::matrices::types::*;
8use crate::matrices::unified::Matrix;
9use crate::simplify::Simplify;
10
11/// QR decomposition implementation
12impl Matrix {
13 /// Perform QR decomposition using Gram-Schmidt process
14 ///
15 /// Decomposes matrix A into A = QR where:
16 /// - Q is orthogonal (Q^T * Q = I)
17 /// - R is upper triangular
18 ///
19 /// # Examples
20 ///
21 /// ```
22 /// use mathhook_core::matrices::Matrix;
23 ///
24 /// let matrix = Matrix::from_arrays([
25 /// [1, 1],
26 /// [0, 1]
27 /// ]);
28 ///
29 /// let qr = matrix.qr_decomposition().unwrap();
30 /// let (q_rows, q_cols) = qr.q.dimensions();
31 /// assert_eq!(q_rows, 2);
32 /// assert_eq!(q_cols, 2);
33 /// ```
34 pub fn qr_decomposition(&self) -> Option<QRDecomposition> {
35 match self {
36 Matrix::Identity(data) => Some(QRDecomposition {
37 q: Matrix::identity(data.size),
38 r: Matrix::identity(data.size),
39 }),
40 Matrix::Zero(data) => Some(QRDecomposition {
41 q: Matrix::identity(data.rows),
42 r: Matrix::zero(data.rows, data.cols),
43 }),
44 _ => {
45 // General QR decomposition using Gram-Schmidt process
46 self.gram_schmidt_qr()
47 }
48 }
49 }
50
51 /// Gram-Schmidt QR decomposition implementation
52 fn gram_schmidt_qr(&self) -> Option<QRDecomposition> {
53 let (rows, cols) = self.dimensions();
54 let mut q_columns: Vec<Vec<Expression>> = Vec::new();
55 let mut r_elements = vec![vec![Expression::integer(0); cols]; cols];
56
57 // Convert columns to vectors for processing
58 for j in 0..cols {
59 let mut column: Vec<Expression> = (0..rows).map(|i| self.get_element(i, j)).collect();
60
61 // Orthogonalize against previous columns
62 for k in 0..j {
63 let dot_product = self.vector_dot(&column, &q_columns[k]);
64 r_elements[k][j] = dot_product.clone();
65
66 // column = column - dot_product * q_k
67 for elem in column.iter_mut().zip(&q_columns[k]) {
68 let (col_elem, q_elem) = elem;
69 let old_val = col_elem.clone();
70 let subtract_val = Expression::mul(vec![dot_product.clone(), q_elem.clone()]);
71 *col_elem = Expression::add(vec![
72 old_val,
73 Expression::mul(vec![Expression::integer(-1), subtract_val]),
74 ])
75 .simplify();
76 }
77 }
78
79 // Normalize the column
80 let norm = self.vector_norm(&column);
81 if norm.is_zero() {
82 return None; // Linearly dependent columns
83 }
84
85 r_elements[j][j] = norm.clone();
86
87 // Normalize column to get q_j
88 let q_column: Vec<Expression> = column
89 .iter()
90 .map(|elem| {
91 // Use canonical form for division: a / b = a * b^(-1)
92 Expression::mul(vec![
93 elem.clone(),
94 Expression::pow(norm.clone(), Expression::integer(-1)),
95 ])
96 .simplify()
97 })
98 .collect();
99 q_columns.push(q_column);
100 }
101
102 // Build Q and R matrices
103 let q_rows: Vec<Vec<Expression>> = (0..rows)
104 .map(|i| {
105 (0..cols)
106 .map(|j| {
107 if j < q_columns.len() {
108 q_columns[j][i].clone()
109 } else {
110 Expression::integer(0)
111 }
112 })
113 .collect()
114 })
115 .collect();
116
117 Some(QRDecomposition {
118 q: Matrix::dense(q_rows),
119 r: Matrix::dense(r_elements),
120 })
121 }
122
123 /// Compute dot product of two vectors (internal helper for QR decomposition)
124 ///
125 /// This is a private helper method. Result: v1 · v2 = sum(v1[i] * v2[i])
126 fn vector_dot(&self, v1: &[Expression], v2: &[Expression]) -> Expression {
127 if v1.len() != v2.len() {
128 return Expression::integer(0);
129 }
130
131 let products: Vec<Expression> = v1
132 .iter()
133 .zip(v2.iter())
134 .map(|(a, b)| Expression::mul(vec![a.clone(), b.clone()]))
135 .collect();
136
137 Expression::add(products).simplify()
138 }
139
140 /// Compute norm of a vector (internal helper for QR decomposition)
141 ///
142 /// This is a private helper method. Result: ||v|| = sqrt(sum(v[i]²))
143 fn vector_norm(&self, v: &[Expression]) -> Expression {
144 let sum_of_squares: Vec<Expression> = v
145 .iter()
146 .map(|x| Expression::pow(x.clone(), Expression::integer(2)))
147 .collect();
148
149 let sum = Expression::add(sum_of_squares).simplify();
150 Expression::pow(sum, Expression::rational(1, 2))
151 }
152}