ndarray_linalg/cholesky.rs
1//! Cholesky decomposition of Hermitian (or real symmetric) positive definite matrices
2//!
3//! See the [Wikipedia page about Cholesky
4//! decomposition](https://en.wikipedia.org/wiki/Cholesky_decomposition) for
5//! more information.
6//!
7//! # Example
8//!
9//! Using the Cholesky decomposition of `A` for various operations, where `A`
10//! is a Hermitian (or real symmetric) positive definite matrix:
11//!
12//! ```
13//! #[macro_use]
14//! extern crate ndarray;
15//! extern crate ndarray_linalg;
16//!
17//! use ndarray::prelude::*;
18//! use ndarray_linalg::cholesky::*;
19//! # fn main() {
20//!
21//! let a: Array2<f64> = array![
22//! [ 4., 12., -16.],
23//! [ 12., 37., -43.],
24//! [-16., -43., 98.]
25//! ];
26//!
27//! // Obtain `L`
28//! let lower = a.cholesky(UPLO::Lower).unwrap();
29//! assert!(lower.abs_diff_eq(&array![
30//! [ 2., 0., 0.],
31//! [ 6., 1., 0.],
32//! [-8., 5., 3.]
33//! ], 1e-9));
34//!
35//! // Find the determinant of `A`
36//! let det = a.detc().unwrap();
37//! assert!((det - 36.).abs() < 1e-9);
38//!
39//! // Solve `A * x = b`
40//! let b = array![4., 13., -11.];
41//! let x = a.solvec(&b).unwrap();
42//! assert!(x.abs_diff_eq(&array![-2., 1., 0.], 1e-9));
43//! # }
44//! ```
45
46use ndarray::*;
47use num_traits::Float;
48
49use crate::convert::*;
50use crate::error::*;
51use crate::layout::*;
52use crate::triangular::IntoTriangular;
53use crate::types::*;
54
55pub use lax::UPLO;
56
57/// Cholesky decomposition of Hermitian (or real symmetric) positive definite matrix
58pub struct CholeskyFactorized<S: Data> {
59 /// `L` from the decomposition `A = L * L^H` or `U` from the decomposition
60 /// `A = U^H * U`.
61 pub factor: ArrayBase<S, Ix2>,
62 /// If this is `UPLO::Lower`, then `self.factor` is `L`. If this is
63 /// `UPLO::Upper`, then `self.factor` is `U`.
64 pub uplo: UPLO,
65}
66
67impl<A, S> CholeskyFactorized<S>
68where
69 A: Scalar + Lapack,
70 S: DataMut<Elem = A>,
71{
72 /// Returns `L` from the Cholesky decomposition `A = L * L^H`.
73 ///
74 /// If `self.uplo == UPLO::Lower`, then no computations need to be
75 /// performed; otherwise, the conjugate transpose of `self.factor` is
76 /// calculated.
77 pub fn into_lower(self) -> ArrayBase<S, Ix2> {
78 match self.uplo {
79 UPLO::Lower => self.factor,
80 UPLO::Upper => self.factor.reversed_axes().mapv_into(|elem| elem.conj()),
81 }
82 }
83
84 /// Returns `U` from the Cholesky decomposition `A = U^H * U`.
85 ///
86 /// If `self.uplo == UPLO::Upper`, then no computations need to be
87 /// performed; otherwise, the conjugate transpose of `self.factor` is
88 /// calculated.
89 pub fn into_upper(self) -> ArrayBase<S, Ix2> {
90 match self.uplo {
91 UPLO::Lower => self.factor.reversed_axes().mapv_into(|elem| elem.conj()),
92 UPLO::Upper => self.factor,
93 }
94 }
95}
96
97impl<A, S> DeterminantC for CholeskyFactorized<S>
98where
99 A: Scalar + Lapack,
100 S: Data<Elem = A>,
101{
102 type Output = <A as Scalar>::Real;
103
104 fn detc(&self) -> Self::Output {
105 Float::exp(self.ln_detc())
106 }
107
108 fn ln_detc(&self) -> Self::Output {
109 self.factor
110 .diag()
111 .iter()
112 .map(|elem| Float::ln(elem.square()))
113 .sum::<Self::Output>()
114 }
115}
116
117impl<A, S> DeterminantCInto for CholeskyFactorized<S>
118where
119 A: Scalar + Lapack,
120 S: Data<Elem = A>,
121{
122 type Output = <A as Scalar>::Real;
123
124 fn detc_into(self) -> Self::Output {
125 self.detc()
126 }
127
128 fn ln_detc_into(self) -> Self::Output {
129 self.ln_detc()
130 }
131}
132
133impl<A, S> InverseC for CholeskyFactorized<S>
134where
135 A: Scalar + Lapack,
136 S: Data<Elem = A>,
137{
138 type Output = Array2<A>;
139
140 fn invc(&self) -> Result<Self::Output> {
141 let f = CholeskyFactorized {
142 factor: replicate(&self.factor),
143 uplo: self.uplo,
144 };
145 f.invc_into()
146 }
147}
148
149impl<A, S> InverseCInto for CholeskyFactorized<S>
150where
151 A: Scalar + Lapack,
152 S: DataMut<Elem = A>,
153{
154 type Output = ArrayBase<S, Ix2>;
155
156 fn invc_into(self) -> Result<Self::Output> {
157 let mut a = self.factor;
158 A::inv_cholesky(a.square_layout()?, self.uplo, a.as_allocated_mut()?)?;
159 triangular_fill_hermitian(&mut a, self.uplo);
160 Ok(a)
161 }
162}
163
164impl<A, S> SolveC<A> for CholeskyFactorized<S>
165where
166 A: Scalar + Lapack,
167 S: Data<Elem = A>,
168{
169 fn solvec_inplace<'a>(&self, b: &'a mut ArrayRef<A, Ix1>) -> Result<&'a mut ArrayRef<A, Ix1>> {
170 A::solve_cholesky(
171 self.factor.square_layout()?,
172 self.uplo,
173 self.factor.as_allocated()?,
174 b.as_slice_mut().unwrap(),
175 )?;
176 Ok(b)
177 }
178}
179
180/// Cholesky decomposition of Hermitian (or real symmetric) positive definite matrix reference
181pub trait Cholesky {
182 type Output;
183
184 /// Computes the Cholesky decomposition of the Hermitian (or real
185 /// symmetric) positive definite matrix.
186 ///
187 /// If the argument is `UPLO::Upper`, then computes the decomposition `A =
188 /// U^H * U` using the upper triangular portion of `A` and returns `U`.
189 /// Otherwise, if the argument is `UPLO::Lower`, computes the decomposition
190 /// `A = L * L^H` using the lower triangular portion of `A` and returns
191 /// `L`.
192 fn cholesky(&self, uplo: UPLO) -> Result<Self::Output>;
193}
194
195/// Cholesky decomposition of Hermitian (or real symmetric) positive definite matrix
196pub trait CholeskyInto {
197 type Output;
198 /// Computes the Cholesky decomposition of the Hermitian (or real
199 /// symmetric) positive definite matrix.
200 ///
201 /// If the argument is `UPLO::Upper`, then computes the decomposition `A =
202 /// U^H * U` using the upper triangular portion of `A` and returns `U`.
203 /// Otherwise, if the argument is `UPLO::Lower`, computes the decomposition
204 /// `A = L * L^H` using the lower triangular portion of `A` and returns
205 /// `L`.
206 fn cholesky_into(self, uplo: UPLO) -> Result<Self::Output>;
207}
208
209/// Cholesky decomposition of Hermitian (or real symmetric) positive definite mutable reference of matrix
210pub trait CholeskyInplace {
211 /// Computes the Cholesky decomposition of the Hermitian (or real
212 /// symmetric) positive definite matrix, writing the result (`L` or `U`
213 /// according to the argument) to `self` and returning it.
214 ///
215 /// If the argument is `UPLO::Upper`, then computes the decomposition `A =
216 /// U^H * U` using the upper triangular portion of `A` and writes `U`.
217 /// Otherwise, if the argument is `UPLO::Lower`, computes the decomposition
218 /// `A = L * L^H` using the lower triangular portion of `A` and writes `L`.
219 fn cholesky_inplace(&mut self, uplo: UPLO) -> Result<&mut Self>;
220}
221
222impl<A> Cholesky for ArrayRef<A, Ix2>
223where
224 A: Scalar + Lapack,
225{
226 type Output = Array2<A>;
227
228 fn cholesky(&self, uplo: UPLO) -> Result<Array2<A>> {
229 let a = replicate(self);
230 a.cholesky_into(uplo)
231 }
232}
233
234impl<A, S> CholeskyInto for ArrayBase<S, Ix2>
235where
236 A: Scalar + Lapack,
237 S: DataMut<Elem = A>,
238{
239 type Output = Self;
240
241 fn cholesky_into(mut self, uplo: UPLO) -> Result<Self> {
242 self.cholesky_inplace(uplo)?;
243 Ok(self)
244 }
245}
246
247impl<A> CholeskyInplace for ArrayRef<A, Ix2>
248where
249 A: Scalar + Lapack,
250{
251 fn cholesky_inplace(&mut self, uplo: UPLO) -> Result<&mut Self> {
252 A::cholesky(self.square_layout()?, uplo, self.as_allocated_mut()?)?;
253 Ok(self.into_triangular(uplo))
254 }
255}
256
257/// Cholesky decomposition of Hermitian (or real symmetric) positive definite matrix reference
258pub trait FactorizeC<S: Data> {
259 /// Computes the Cholesky decomposition of the Hermitian (or real
260 /// symmetric) positive definite matrix.
261 ///
262 /// If the argument is `UPLO::Upper`, then computes the decomposition `A =
263 /// U^H * U` using the upper triangular portion of `A` and returns the
264 /// factorization containing `U`. Otherwise, if the argument is
265 /// `UPLO::Lower`, computes the decomposition `A = L * L^H` using the lower
266 /// triangular portion of `A` and returns the factorization containing `L`.
267 fn factorizec(&self, uplo: UPLO) -> Result<CholeskyFactorized<S>>;
268}
269
270/// Cholesky decomposition of Hermitian (or real symmetric) positive definite matrix
271pub trait FactorizeCInto<S: Data> {
272 /// Computes the Cholesky decomposition of the Hermitian (or real
273 /// symmetric) positive definite matrix.
274 ///
275 /// If the argument is `UPLO::Upper`, then computes the decomposition `A =
276 /// U^H * U` using the upper triangular portion of `A` and returns the
277 /// factorization containing `U`. Otherwise, if the argument is
278 /// `UPLO::Lower`, computes the decomposition `A = L * L^H` using the lower
279 /// triangular portion of `A` and returns the factorization containing `L`.
280 fn factorizec_into(self, uplo: UPLO) -> Result<CholeskyFactorized<S>>;
281}
282
283impl<A, S> FactorizeCInto<S> for ArrayBase<S, Ix2>
284where
285 A: Scalar + Lapack,
286 S: DataMut<Elem = A>,
287{
288 fn factorizec_into(self, uplo: UPLO) -> Result<CholeskyFactorized<S>> {
289 Ok(CholeskyFactorized {
290 factor: self.cholesky_into(uplo)?,
291 uplo,
292 })
293 }
294}
295
296impl<A> FactorizeC<OwnedRepr<A>> for ArrayRef<A, Ix2>
297where
298 A: Scalar + Lapack,
299{
300 fn factorizec(&self, uplo: UPLO) -> Result<CholeskyFactorized<OwnedRepr<A>>> {
301 Ok(CholeskyFactorized {
302 factor: self.cholesky(uplo)?,
303 uplo,
304 })
305 }
306}
307
308/// Solve systems of linear equations with Hermitian (or real symmetric)
309/// positive definite coefficient matrices
310pub trait SolveC<A: Scalar> {
311 /// Solves a system of linear equations `A * x = b` with Hermitian (or real
312 /// symmetric) positive definite matrix `A`, where `A` is `self`, `b` is
313 /// the argument, and `x` is the successful result.
314 fn solvec(&self, b: &ArrayRef<A, Ix1>) -> Result<Array1<A>> {
315 let mut b = replicate(b);
316 self.solvec_inplace(&mut b)?;
317 Ok(b)
318 }
319 /// Solves a system of linear equations `A * x = b` with Hermitian (or real
320 /// symmetric) positive definite matrix `A`, where `A` is `self`, `b` is
321 /// the argument, and `x` is the successful result.
322 fn solvec_into<S: DataMut<Elem = A>>(
323 &self,
324 mut b: ArrayBase<S, Ix1>,
325 ) -> Result<ArrayBase<S, Ix1>> {
326 self.solvec_inplace(&mut b)?;
327 Ok(b)
328 }
329 /// Solves a system of linear equations `A * x = b` with Hermitian (or real
330 /// symmetric) positive definite matrix `A`, where `A` is `self`, `b` is
331 /// the argument, and `x` is the successful result. The value of `x` is
332 /// also assigned to the argument.
333 fn solvec_inplace<'a>(&self, b: &'a mut ArrayRef<A, Ix1>) -> Result<&'a mut ArrayRef<A, Ix1>>;
334}
335
336impl<A> SolveC<A> for ArrayRef<A, Ix2>
337where
338 A: Scalar + Lapack,
339{
340 fn solvec_inplace<'a>(&self, b: &'a mut ArrayRef<A, Ix1>) -> Result<&'a mut ArrayRef<A, Ix1>> {
341 self.factorizec(UPLO::Upper)?.solvec_inplace(b)
342 }
343}
344
345/// Inverse of Hermitian (or real symmetric) positive definite matrix ref
346pub trait InverseC {
347 type Output;
348 /// Computes the inverse of the Hermitian (or real symmetric) positive
349 /// definite matrix.
350 fn invc(&self) -> Result<Self::Output>;
351}
352
353/// Inverse of Hermitian (or real symmetric) positive definite matrix
354pub trait InverseCInto {
355 type Output;
356 /// Computes the inverse of the Hermitian (or real symmetric) positive
357 /// definite matrix.
358 fn invc_into(self) -> Result<Self::Output>;
359}
360
361impl<A> InverseC for ArrayRef<A, Ix2>
362where
363 A: Scalar + Lapack,
364{
365 type Output = Array2<A>;
366
367 fn invc(&self) -> Result<Self::Output> {
368 self.factorizec(UPLO::Upper)?.invc_into()
369 }
370}
371
372impl<A, S> InverseCInto for ArrayBase<S, Ix2>
373where
374 A: Scalar + Lapack,
375 S: DataMut<Elem = A>,
376{
377 type Output = Self;
378
379 fn invc_into(self) -> Result<Self::Output> {
380 self.factorizec_into(UPLO::Upper)?.invc_into()
381 }
382}
383
384/// Determinant of Hermitian (or real symmetric) positive definite matrix ref
385pub trait DeterminantC {
386 type Output;
387
388 /// Computes the determinant of the Hermitian (or real symmetric) positive
389 /// definite matrix.
390 fn detc(&self) -> Self::Output;
391
392 /// Computes the natural log of the determinant of the Hermitian (or real
393 /// symmetric) positive definite matrix.
394 ///
395 /// This method is more robust than `.detc()` to very small or very large
396 /// determinants since it returns the natural logarithm of the determinant
397 /// rather than the determinant itself.
398 fn ln_detc(&self) -> Self::Output;
399}
400
401/// Determinant of Hermitian (or real symmetric) positive definite matrix
402pub trait DeterminantCInto {
403 type Output;
404
405 /// Computes the determinant of the Hermitian (or real symmetric) positive
406 /// definite matrix.
407 fn detc_into(self) -> Self::Output;
408
409 /// Computes the natural log of the determinant of the Hermitian (or real
410 /// symmetric) positive definite matrix.
411 ///
412 /// This method is more robust than `.detc_into()` to very small or very
413 /// large determinants since it returns the natural logarithm of the
414 /// determinant rather than the determinant itself.
415 fn ln_detc_into(self) -> Self::Output;
416}
417
418impl<A> DeterminantC for ArrayRef<A, Ix2>
419where
420 A: Scalar + Lapack,
421{
422 type Output = Result<<A as Scalar>::Real>;
423
424 fn detc(&self) -> Self::Output {
425 Ok(Float::exp(self.ln_detc()?))
426 }
427
428 fn ln_detc(&self) -> Self::Output {
429 Ok(self.factorizec(UPLO::Upper)?.ln_detc())
430 }
431}
432
433impl<A, S> DeterminantCInto for ArrayBase<S, Ix2>
434where
435 A: Scalar + Lapack,
436 S: DataMut<Elem = A>,
437{
438 type Output = Result<<A as Scalar>::Real>;
439
440 fn detc_into(self) -> Self::Output {
441 Ok(Float::exp(self.ln_detc_into()?))
442 }
443
444 fn ln_detc_into(self) -> Self::Output {
445 Ok(self.factorizec_into(UPLO::Upper)?.ln_detc_into())
446 }
447}