mathhook_core/matrices/decomposition/cholesky.rs
1//! Cholesky decomposition algorithms
2//!
3//! This module provides Cholesky decomposition for symmetric positive definite
4//! matrices, useful for solving linear systems and optimization problems.
5
6use crate::core::Expression;
7use crate::matrices::types::*;
8use crate::matrices::unified::Matrix;
9use crate::simplify::Simplify;
10
11/// Cholesky decomposition implementation
12impl Matrix {
13 /// Perform Cholesky decomposition for positive definite matrices
14 ///
15 /// Decomposes symmetric positive definite matrix A into A = LL^T where:
16 /// - L is lower triangular with positive diagonal elements
17 ///
18 /// # Examples
19 ///
20 /// ```
21 /// use mathhook_core::matrices::Matrix;
22 /// use mathhook_core::Expression;
23 ///
24 /// let matrix = Matrix::from_arrays([
25 /// [4, 2],
26 /// [2, 3]
27 /// ]);
28 ///
29 /// if let Some(chol) = matrix.cholesky_decomposition() {
30 /// let (l_rows, l_cols) = chol.l.dimensions();
31 /// assert_eq!(l_rows, 2);
32 /// assert_eq!(l_cols, 2);
33 /// }
34 /// ```
35 pub fn cholesky_decomposition(&self) -> Option<CholeskyDecomposition> {
36 let (rows, cols) = self.dimensions();
37 if rows != cols || !self.is_symmetric() {
38 return None;
39 }
40
41 match self {
42 Matrix::Identity(data) => Some(CholeskyDecomposition {
43 l: Matrix::identity(data.size),
44 }),
45 Matrix::Scalar(data) => {
46 let sqrt_c = Expression::pow(data.scalar_value.clone(), Expression::rational(1, 2));
47 Some(CholeskyDecomposition {
48 l: Matrix::scalar(data.size, sqrt_c),
49 })
50 }
51 Matrix::Diagonal(data) => {
52 let sqrt_elements: Vec<Expression> = data
53 .diagonal_elements
54 .iter()
55 .map(|elem| Expression::pow(elem.clone(), Expression::rational(1, 2)))
56 .collect();
57 Some(CholeskyDecomposition {
58 l: Matrix::diagonal(sqrt_elements),
59 })
60 }
61 _ => {
62 // General Cholesky decomposition
63 self.general_cholesky()
64 }
65 }
66 }
67
68 /// General Cholesky decomposition implementation
69 fn general_cholesky(&self) -> Option<CholeskyDecomposition> {
70 let (n, _) = self.dimensions();
71 let mut l_elements = vec![vec![Expression::integer(0); n]; n];
72
73 for i in 0..n {
74 for j in 0..=i {
75 if i == j {
76 // L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2 for k < i))
77 let sum =
78 l_elements[i]
79 .iter()
80 .take(i)
81 .fold(Expression::integer(0), |acc, l_ik| {
82 Expression::add(vec![
83 acc,
84 Expression::pow(l_ik.clone(), Expression::integer(2)),
85 ])
86 .simplify()
87 });
88
89 let diagonal_val = Expression::add(vec![
90 self.get_element(i, i),
91 Expression::mul(vec![Expression::integer(-1), sum]),
92 ])
93 .simplify();
94
95 l_elements[i][i] = Expression::pow(diagonal_val, Expression::rational(1, 2));
96 } else {
97 // L[i][j] = (A[i][j] - sum(L[i][k]*L[j][k] for k < j)) / L[j][j]
98 let sum = l_elements[i].iter().zip(l_elements[j].iter()).take(j).fold(
99 Expression::integer(0),
100 |acc, (l_ik, l_jk)| {
101 Expression::add(vec![
102 acc,
103 Expression::mul(vec![l_ik.clone(), l_jk.clone()]),
104 ])
105 .simplify()
106 },
107 );
108
109 let numerator = Expression::add(vec![
110 self.get_element(i, j),
111 Expression::mul(vec![Expression::integer(-1), sum]),
112 ])
113 .simplify();
114
115 // Use canonical form for division: a / b = a * b^(-1)
116 l_elements[i][j] = Expression::mul(vec![
117 numerator,
118 Expression::pow(l_elements[j][j].clone(), Expression::integer(-1)),
119 ])
120 .simplify();
121 }
122 }
123 }
124
125 Some(CholeskyDecomposition {
126 l: Matrix::dense(l_elements),
127 })
128 }
129
130 /// Check if matrix is positive definite using Cholesky test
131 ///
132 /// # Examples
133 ///
134 /// ```
135 /// use mathhook_core::matrices::Matrix;
136 /// use mathhook_core::Expression;
137 ///
138 /// let identity = Matrix::identity(3);
139 /// assert!(identity.is_positive_definite_cholesky());
140 ///
141 /// let scalar = Matrix::scalar(2, Expression::integer(5));
142 /// assert!(scalar.is_positive_definite_cholesky());
143 /// ```
144 pub fn is_positive_definite_cholesky(&self) -> bool {
145 if !self.is_symmetric() {
146 return false;
147 }
148
149 match self {
150 Matrix::Identity(_) => true,
151 Matrix::Scalar(data) => {
152 // Check if scalar_value > 0 (simplified check)
153 !data.scalar_value.is_zero() && data.scalar_value != Expression::integer(-1)
154 }
155 Matrix::Diagonal(data) => {
156 // Check if all diagonal elements > 0 (simplified check)
157 data.diagonal_elements.iter().all(|elem| !elem.is_zero())
158 }
159 _ => {
160 // Use Cholesky decomposition test
161 self.cholesky_decomposition().is_some()
162 }
163 }
164 }
165}