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 // Diagonal element: L[i][i] = sqrt(A[i][i] - sum(L[i][k]^2 for k < i))
77 let mut sum = Expression::integer(0);
78 for k in 0..i {
79 let l_ik = l_elements[i][k].clone();
80 sum = Expression::add(vec![
81 sum,
82 Expression::pow(l_ik, Expression::integer(2)),
83 ])
84 .simplify();
85 }
86
87 let diagonal_val = Expression::add(vec![
88 self.get_element(i, i),
89 Expression::mul(vec![Expression::integer(-1), sum]),
90 ])
91 .simplify();
92
93 l_elements[i][i] = Expression::pow(diagonal_val, Expression::rational(1, 2));
94 } else {
95 // Lower triangular element: L[i][j] = (A[i][j] - sum(L[i][k]*L[j][k] for k < j)) / L[j][j]
96 let mut sum = Expression::integer(0);
97 for k in 0..j {
98 let l_ik = l_elements[i][k].clone();
99 let l_jk = l_elements[j][k].clone();
100 sum = Expression::add(vec![sum, Expression::mul(vec![l_ik, l_jk])])
101 .simplify();
102 }
103
104 let numerator = Expression::add(vec![
105 self.get_element(i, j),
106 Expression::mul(vec![Expression::integer(-1), sum]),
107 ])
108 .simplify();
109
110 // Use canonical form for division: a / b = a * b^(-1)
111 l_elements[i][j] = Expression::mul(vec![
112 numerator,
113 Expression::pow(l_elements[j][j].clone(), Expression::integer(-1)),
114 ])
115 .simplify();
116 }
117 }
118 }
119
120 Some(CholeskyDecomposition {
121 l: Matrix::dense(l_elements),
122 })
123 }
124
125 /// Check if matrix is positive definite using Cholesky test
126 ///
127 /// # Examples
128 ///
129 /// ```
130 /// use mathhook_core::matrices::Matrix;
131 /// use mathhook_core::Expression;
132 ///
133 /// let identity = Matrix::identity(3);
134 /// assert!(identity.is_positive_definite_cholesky());
135 ///
136 /// let scalar = Matrix::scalar(2, Expression::integer(5));
137 /// assert!(scalar.is_positive_definite_cholesky());
138 /// ```
139 pub fn is_positive_definite_cholesky(&self) -> bool {
140 if !self.is_symmetric() {
141 return false;
142 }
143
144 match self {
145 Matrix::Identity(_) => true,
146 Matrix::Scalar(data) => {
147 // Check if scalar_value > 0 (simplified check)
148 !data.scalar_value.is_zero() && data.scalar_value != Expression::integer(-1)
149 }
150 Matrix::Diagonal(data) => {
151 // Check if all diagonal elements > 0 (simplified check)
152 data.diagonal_elements.iter().all(|elem| !elem.is_zero())
153 }
154 _ => {
155 // Use Cholesky decomposition test
156 self.cholesky_decomposition().is_some()
157 }
158 }
159 }
160}