faer_cholesky/llt/
mod.rs

1//! The Cholesky decomposition of a hermitian positive definite matrix $A$ is such that:
2//! $$A = LL^H,$$
3//! where $L$ is a lower triangular matrix.
4
5pub mod compute;
6pub mod inverse;
7pub mod reconstruct;
8pub mod solve;
9pub mod update;
10
11/// This error signifies that the LLT decomposition could not be computed due to the matrix not
12/// being numerically positive definite.
13#[derive(Debug, Clone, Copy)]
14pub struct CholeskyError {
15    /// The dimension of the first square non positive-definite top-left corner of the input
16    /// matrix.
17    pub non_positive_definite_minor: usize,
18}
19
20#[cfg(test)]
21mod tests {
22    use assert_approx_eq::assert_approx_eq;
23    use dyn_stack::{GlobalPodBuffer, PodStack};
24
25    use super::{compute::*, inverse::*, reconstruct::*, solve::*, update::*};
26    use faer_core::{c64, mul, ComplexField, Conj, Mat, MatRef, Parallelism};
27
28    type E = c64;
29
30    fn reconstruct_matrix(cholesky_factor: MatRef<'_, E>) -> Mat<E> {
31        let n = cholesky_factor.nrows();
32
33        let mut a_reconstructed = Mat::zeros(n, n);
34        reconstruct_lower(
35            a_reconstructed.as_mut(),
36            cholesky_factor,
37            Parallelism::Rayon(0),
38            PodStack::new(&mut GlobalPodBuffer::new(
39                reconstruct_lower_req::<E>(n).unwrap(),
40            )),
41        );
42
43        a_reconstructed
44    }
45
46    fn random() -> E {
47        E {
48            re: rand::random(),
49            im: rand::random(),
50        }
51    }
52
53    fn random_positive_definite(n: usize) -> Mat<E> {
54        let a = Mat::from_fn(n, n, |_, _| random());
55        let mut ata = Mat::zeros(n, n);
56
57        mul::matmul(
58            ata.as_mut(),
59            a.as_ref().adjoint(),
60            a.as_ref(),
61            None,
62            E::faer_one(),
63            Parallelism::Rayon(8),
64        );
65
66        ata
67    }
68
69    #[test]
70    fn test_roundtrip() {
71        for n in (0..32).chain((2..32).map(|i| i * 16)) {
72            let mut a = random_positive_definite(n);
73            let a_orig = a.clone();
74            cholesky_in_place(
75                a.as_mut(),
76                Default::default(),
77                Parallelism::Rayon(8),
78                PodStack::new(&mut []),
79                Default::default(),
80            )
81            .unwrap();
82            let mut a_reconstructed = reconstruct_matrix(a.as_ref());
83            let mut inv = Mat::zeros(n, n);
84            invert_lower(
85                inv.as_mut(),
86                a.as_ref(),
87                Parallelism::Rayon(0),
88                PodStack::new(&mut GlobalPodBuffer::new(
89                    invert_lower_req::<E>(n, Parallelism::Rayon(0)).unwrap(),
90                )),
91            );
92
93            for j in 0..n {
94                for i in 0..j {
95                    a_reconstructed.write(i, j, a_reconstructed.read(j, i).faer_conj());
96                    inv.write(i, j, inv.read(j, i).faer_conj());
97                }
98            }
99
100            let mut prod = Mat::zeros(n, n);
101            mul::matmul(
102                prod.as_mut(),
103                a_reconstructed.as_ref(),
104                inv.as_ref(),
105                None,
106                E::faer_one(),
107                Parallelism::Rayon(0),
108            );
109
110            for j in 0..n {
111                for i in j..n {
112                    assert_approx_eq!(a_reconstructed.read(i, j), a_orig.read(i, j));
113                }
114            }
115
116            for j in 0..n {
117                for i in 0..n {
118                    let target = if i == j {
119                        E::faer_one()
120                    } else {
121                        E::faer_zero()
122                    };
123                    assert_approx_eq!(prod.read(i, j), target);
124                }
125            }
126        }
127    }
128
129    #[test]
130    fn test_solve() {
131        for n in 0..20 {
132            let k = 5;
133            let mut a = random_positive_definite(n);
134            let mut rhs = Mat::from_fn(n, k, |_, _| random());
135            let a_orig = a.clone();
136            let rhs_orig = rhs.clone();
137
138            cholesky_in_place(
139                a.as_mut(),
140                Default::default(),
141                Parallelism::Rayon(8),
142                PodStack::new(&mut []),
143                Default::default(),
144            )
145            .unwrap();
146            solve_in_place_with_conj(
147                a.as_ref(),
148                Conj::No,
149                rhs.as_mut(),
150                Parallelism::Rayon(8),
151                PodStack::new(&mut []),
152            );
153
154            let mut result = Mat::zeros(n, k);
155            use mul::triangular::BlockStructure::*;
156            mul::triangular::matmul(
157                result.as_mut(),
158                Rectangular,
159                a_orig.as_ref(),
160                TriangularLower,
161                rhs.as_ref(),
162                Rectangular,
163                None,
164                E::faer_one(),
165                Parallelism::Rayon(8),
166            );
167
168            mul::triangular::matmul(
169                result.as_mut(),
170                Rectangular,
171                a_orig.as_ref().adjoint(),
172                StrictTriangularUpper,
173                rhs.as_ref(),
174                Rectangular,
175                Some(E::faer_one()),
176                E::faer_one(),
177                Parallelism::Rayon(8),
178            );
179
180            for j in 0..k {
181                for i in 0..n {
182                    assert_approx_eq!(result.read(i, j), rhs_orig.read(i, j), 1e-3);
183                }
184            }
185        }
186    }
187
188    #[test]
189    fn test_update() {
190        use mul::triangular::BlockStructure::*;
191        for k in [0, 1, 2, 3, 4, 5] {
192            let n = 4;
193            let mut a = random_positive_definite(n);
194            let mut a_updated = a.clone();
195            let mut w = Mat::from_fn(n, k, |_, _| random());
196            let mut alpha = Mat::from_fn(k, 1, |_, _| E::faer_from_real(rand::random()));
197            let alpha = alpha.as_mut().col_mut(0).as_2d_mut();
198
199            let mut w_alpha = Mat::zeros(n, k);
200            for j in 0..k {
201                for i in 0..n {
202                    w_alpha.write(i, j, alpha.read(j, 0).faer_mul(w.read(i, j)));
203                }
204            }
205
206            mul::triangular::matmul(
207                a_updated.as_mut(),
208                TriangularLower,
209                w_alpha.as_ref(),
210                Rectangular,
211                w.as_ref().adjoint(),
212                Rectangular,
213                Some(E::faer_one()),
214                E::faer_one(),
215                Parallelism::Rayon(8),
216            );
217
218            cholesky_in_place(
219                a.as_mut(),
220                Default::default(),
221                Parallelism::Rayon(8),
222                PodStack::new(&mut []),
223                Default::default(),
224            )
225            .unwrap();
226
227            rank_r_update_clobber(a.as_mut(), w.as_mut(), alpha).unwrap();
228
229            let a_reconstructed = reconstruct_matrix(a.as_ref());
230
231            for j in 0..n {
232                for i in j..n {
233                    assert_approx_eq!(a_reconstructed.read(i, j), a_updated.read(i, j), 1e-4);
234                }
235            }
236        }
237    }
238
239    #[test]
240    fn test_delete() {
241        let a_orig = random_positive_definite(4);
242
243        {
244            let mut a = a_orig.clone();
245            let n = a.nrows();
246            let r = 2;
247
248            cholesky_in_place(
249                a.as_mut(),
250                Default::default(),
251                Parallelism::Rayon(8),
252                PodStack::new(&mut []),
253                Default::default(),
254            )
255            .unwrap();
256
257            delete_rows_and_cols_clobber(
258                a.as_mut(),
259                &mut [1, 3],
260                Parallelism::Rayon(8),
261                PodStack::new(&mut GlobalPodBuffer::new(
262                    delete_rows_and_cols_clobber_req::<E>(n, r, Parallelism::Rayon(8)).unwrap(),
263                )),
264            );
265
266            let a_reconstructed = reconstruct_matrix(a.as_ref().submatrix(0, 0, n - r, n - r));
267            assert_approx_eq!(a_reconstructed.read(0, 0), a_orig.read(0, 0));
268            assert_approx_eq!(a_reconstructed.read(1, 0), a_orig.read(2, 0));
269            assert_approx_eq!(a_reconstructed.read(1, 1), a_orig.read(2, 2));
270        }
271
272        {
273            let mut a = a_orig.clone();
274            let n = a.nrows();
275            let r = 2;
276
277            cholesky_in_place(
278                a.as_mut(),
279                Default::default(),
280                Parallelism::Rayon(8),
281                PodStack::new(&mut []),
282                Default::default(),
283            )
284            .unwrap();
285
286            delete_rows_and_cols_clobber(
287                a.as_mut(),
288                &mut [0, 2],
289                Parallelism::Rayon(8),
290                PodStack::new(&mut GlobalPodBuffer::new(
291                    delete_rows_and_cols_clobber_req::<E>(n, r, Parallelism::Rayon(8)).unwrap(),
292                )),
293            );
294
295            let a_reconstructed = reconstruct_matrix(a.as_ref().submatrix(0, 0, n - r, n - r));
296            assert_approx_eq!(a_reconstructed.read(0, 0), a_orig.read(1, 1));
297            assert_approx_eq!(a_reconstructed.read(1, 0), a_orig.read(3, 1));
298            assert_approx_eq!(a_reconstructed.read(1, 1), a_orig.read(3, 3));
299        }
300
301        {
302            let mut a = a_orig.clone();
303            let n = a.nrows();
304            let r = 3;
305
306            cholesky_in_place(
307                a.as_mut(),
308                Default::default(),
309                Parallelism::Rayon(8),
310                PodStack::new(&mut []),
311                Default::default(),
312            )
313            .unwrap();
314
315            delete_rows_and_cols_clobber(
316                a.as_mut(),
317                &mut [0, 2, 3],
318                Parallelism::Rayon(8),
319                PodStack::new(&mut GlobalPodBuffer::new(
320                    delete_rows_and_cols_clobber_req::<E>(n, r, Parallelism::Rayon(8)).unwrap(),
321                )),
322            );
323
324            let a_reconstructed = reconstruct_matrix(a.as_ref().submatrix(0, 0, n - r, n - r));
325            assert_approx_eq!(a_reconstructed.read(0, 0), a_orig.read(1, 1));
326        }
327    }
328
329    #[test]
330    fn test_insert() {
331        let n = 4;
332        let r = 2;
333
334        let a_orig = random_positive_definite(n + r);
335
336        for position in [0, 2, 4] {
337            let mut a = a_orig.clone();
338            let mut w = Mat::zeros(n + r, r);
339
340            for j in 0..r {
341                for i in 0..n + r {
342                    w.write(i, j, a.read(i, j + position));
343                }
344            }
345
346            cholesky_in_place(
347                a.as_mut(),
348                Default::default(),
349                Parallelism::Rayon(8),
350                PodStack::new(&mut []),
351                Default::default(),
352            )
353            .unwrap();
354
355            delete_rows_and_cols_clobber(
356                a.as_mut(),
357                &mut [position, position + 1],
358                Parallelism::Rayon(8),
359                PodStack::new(&mut GlobalPodBuffer::new(
360                    delete_rows_and_cols_clobber_req::<f64>(n + r, r, Parallelism::Rayon(8))
361                        .unwrap(),
362                )),
363            );
364
365            insert_rows_and_cols_clobber(
366                a.as_mut(),
367                position,
368                w.as_mut(),
369                Parallelism::Rayon(8),
370                PodStack::new(&mut GlobalPodBuffer::new(
371                    insert_rows_and_cols_clobber_req::<E>(r, Parallelism::Rayon(8)).unwrap(),
372                )),
373            )
374            .unwrap();
375
376            let a_reconstructed = reconstruct_matrix(a.as_ref());
377            for j in 0..n + r {
378                for i in j..n + r {
379                    assert_approx_eq!(a_reconstructed.read(i, j), a_orig.read(i, j), 1e-4);
380                }
381            }
382        }
383    }
384}