sprs_ldl/
lib.rs

1///! Cholesky factorization module.
2///!
3///! Contains LDLT decomposition methods.
4///!
5///! This decomposition operates on symmetric positive definite matrices,
6///! and is written `A = L D L` where L is lower triangular and D is diagonal.
7///! It is closely related to the Cholesky decomposition, but is often more
8///! numerically stable and can work on some indefinite matrices.
9///!
10///! The easiest way to use this API is to create a `LdlNumeric` instance from
11///! a matrix, then use the `LdlNumeric::solve` method.
12///!
13///! It is possible to update a decomposition if the sparsity structure of a
14///! matrix does not change. In that case the `LdlNumeric::update` method can
15///! be used.
16///!
17///! When only the sparsity structure of a matrix is known, it is possible
18///! to precompute part of the factorization by using the `LdlSymbolic` struct.
19///! This struct can the be converted into a `LdlNumeric` once the non-zero
20///! values are known, using the `LdlSymbolic::factor` method.
21// This method is adapted from the LDL library by Tim Davis:
22//
23// LDL Copyright (c) 2005 by Timothy A. Davis.  All Rights Reserved.
24//
25// LDL License:
26//
27//     Your use or distribution of LDL or any modified version of
28//     LDL implies that you agree to this License.
29//
30//     This library is free software; you can redistribute it and/or
31//     modify it under the terms of the GNU Lesser General Public
32//     License as published by the Free Software Foundation; either
33//     version 2.1 of the License, or (at your option) any later version.
34//
35//     This library is distributed in the hope that it will be useful,
36//     but WITHOUT ANY WARRANTY; without even the implied warranty of
37//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
38//     Lesser General Public License for more details.
39//
40//     You should have received a copy of the GNU Lesser General Public
41//     License along with this library; if not, write to the Free Software
42//     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
43//     USA
44//
45//     Permission is hereby granted to use or copy this program under the
46//     terms of the GNU LGPL, provided that the Copyright, this License,
47//     and the Availability of the original version is retained on all copies.
48//     User documentation of any code that uses this code or any modified
49//     version of this code must cite the Copyright, this License, the
50//     Availability note, and "Used by permission." Permission to modify
51//     the code and to distribute modified code is granted, provided the
52//     Copyright, this License, and the Availability note are retained,
53//     and a notice that the code was modified is included.
54use std::ops::Deref;
55
56use num_traits::Num;
57
58use sprs::errors::{LinalgError, SingularMatrixInfo};
59use sprs::indexing::SpIndex;
60use sprs::linalg;
61use sprs::stack::DStack;
62use sprs::{is_symmetric, CsMatViewI, PermOwnedI, Permutation};
63use sprs::{DenseVector, DenseVectorMut};
64use sprs::{FillInReduction, PermutationCheck, SymmetryCheck};
65
66#[cfg(feature = "sprs_suitesparse_ldl")]
67use sprs_suitesparse_ldl::LdlNumeric as LdlNumericC;
68#[cfg(feature = "sprs_suitesparse_ldl")]
69use sprs_suitesparse_ldl::LdlSymbolic as LdlSymbolicC;
70#[cfg(feature = "sprs_suitesparse_ldl")]
71use sprs_suitesparse_ldl::{LdlLongNumeric, LdlLongSymbolic};
72
73/// Builder pattern structure to customize a LDLT decomposition
74#[derive(Copy, Clone, Eq, PartialEq, Debug)]
75pub struct Ldl {
76    check_symmetry: SymmetryCheck,
77    check_perm: PermutationCheck,
78    fill_red_method: FillInReduction,
79}
80
81impl Default for Ldl {
82    fn default() -> Self {
83        Self {
84            check_symmetry: SymmetryCheck::CheckSymmetry,
85            fill_red_method: FillInReduction::ReverseCuthillMcKee,
86            check_perm: PermutationCheck::CheckPerm,
87        }
88    }
89}
90
91/// Structure to compute and hold a symbolic LDLT decomposition
92#[derive(Debug, Clone)]
93pub struct LdlSymbolic<I> {
94    colptr: Vec<I>,
95    parents: linalg::etree::ParentsOwned,
96    nz: Vec<I>,
97    flag_workspace: Vec<I>,
98    perm: Permutation<I, Vec<I>>,
99}
100
101/// Structure to hold a numeric LDLT decomposition
102#[derive(Debug, Clone)]
103pub struct LdlNumeric<N, I> {
104    symbolic: LdlSymbolic<I>,
105    l_indices: Vec<I>,
106    l_data: Vec<N>,
107    diag: Vec<N>,
108    y_workspace: Vec<N>,
109    pattern_workspace: DStack<I>,
110}
111
112impl Ldl {
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    pub fn check_symmetry(self, check: SymmetryCheck) -> Self {
118        Self {
119            check_symmetry: check,
120            ..self
121        }
122    }
123
124    pub fn check_perm(self, check: PermutationCheck) -> Self {
125        Self {
126            check_perm: check,
127            ..self
128        }
129    }
130
131    pub fn fill_in_reduction(self, method: FillInReduction) -> Self {
132        Self {
133            fill_red_method: method,
134            ..self
135        }
136    }
137
138    pub fn perm<N, I>(&self, mat: CsMatViewI<N, I>) -> PermOwnedI<I>
139    where
140        I: SpIndex,
141    {
142        match self.fill_red_method {
143            FillInReduction::NoReduction => PermOwnedI::identity(mat.rows()),
144            FillInReduction::ReverseCuthillMcKee => {
145                sprs::linalg::reverse_cuthill_mckee(mat.structure_view()).perm
146            }
147            FillInReduction::CAMDSuiteSparse => {
148                #[cfg(not(feature = "sprs_suitesparse_camd"))]
149                panic!(
150                    "Unavailable without the `sprs_suitesparse_camd` feature"
151                );
152                #[cfg(feature = "sprs_suitesparse_camd")]
153                sprs_suitesparse_camd::camd(mat.structure_view())
154            }
155            _ => {
156                unreachable!(
157                    "Unhandled method, report a bug at https://github.com/vbarrielle/sprs/issues/199"
158                )
159            }
160        }
161    }
162
163    pub fn symbolic<N, I>(self, mat: CsMatViewI<N, I>) -> LdlSymbolic<I>
164    where
165        I: SpIndex,
166        N: Copy + PartialEq,
167    {
168        LdlSymbolic::new_perm(mat, self.perm(mat), self.check_symmetry)
169    }
170
171    #[cfg(feature = "sprs_suitesparse_ldl")]
172    pub fn symbolic_c<N, I>(self, mat: CsMatViewI<N, I>) -> LdlSymbolicC
173    where
174        I: SpIndex,
175        N: Copy + PartialEq + Into<f64>,
176    {
177        LdlSymbolicC::new_perm(mat, self.perm(mat), self.check_perm)
178    }
179
180    #[cfg(feature = "sprs_suitesparse_ldl")]
181    pub fn symbolic_c_long<N, I>(self, mat: CsMatViewI<N, I>) -> LdlLongSymbolic
182    where
183        I: SpIndex,
184        N: Copy + PartialEq + Into<f64>,
185    {
186        LdlLongSymbolic::new_perm(mat, self.perm(mat), self.check_perm)
187    }
188
189    pub fn numeric<N, I>(
190        self,
191        mat: CsMatViewI<N, I>,
192    ) -> Result<LdlNumeric<N, I>, LinalgError>
193    where
194        I: SpIndex,
195        N: Copy + Num + PartialOrd,
196    {
197        // self.symbolic(mat).factor(mat)
198        let symb = self.symbolic(mat);
199        symb.factor(mat)
200    }
201
202    #[cfg(feature = "sprs_suitesparse_ldl")]
203    pub fn numeric_c<N, I>(
204        self,
205        mat: CsMatViewI<N, I>,
206    ) -> Result<LdlNumericC, LinalgError>
207    where
208        I: SpIndex,
209        N: Copy + Num + PartialOrd + Into<f64>,
210    {
211        self.symbolic_c(mat).factor(mat)
212    }
213
214    #[cfg(feature = "sprs_suitesparse_ldl")]
215    pub fn numeric_c_long<N, I>(
216        self,
217        mat: CsMatViewI<N, I>,
218    ) -> Result<LdlLongNumeric, LinalgError>
219    where
220        I: SpIndex,
221        N: Copy + Num + PartialOrd + Into<f64>,
222    {
223        self.symbolic_c_long(mat).factor(mat)
224    }
225}
226
227impl<I: SpIndex> LdlSymbolic<I> {
228    /// Compute the symbolic LDLT of the given matrix
229    ///
230    /// # Panics
231    ///
232    /// * if mat is not symmetric
233    pub fn new<N>(mat: CsMatViewI<N, I>) -> Self
234    where
235        N: Copy + PartialEq,
236    {
237        assert_eq!(mat.rows(), mat.cols());
238        let perm: Permutation<I, Vec<I>> = Permutation::identity(mat.rows());
239        Self::new_perm(mat, perm, SymmetryCheck::CheckSymmetry)
240    }
241
242    /// Compute the symbolic decomposition L D L^T = P A P^T
243    /// where P is a permutation matrix.
244    ///
245    /// Using a good permutation matrix can reduce the non-zero count in L,
246    /// thus making the decomposition and the solves faster.
247    ///
248    /// # Panics
249    ///
250    /// * if mat is not symmetric
251    pub fn new_perm<N>(
252        mat: CsMatViewI<N, I>,
253        perm: PermOwnedI<I>,
254        check_symmetry: SymmetryCheck,
255    ) -> Self
256    where
257        N: Copy + PartialEq,
258        I: SpIndex,
259    {
260        let n = mat.cols();
261        assert!(mat.rows() == n, "matrix should be square");
262        let mut l_colptr = vec![I::zero(); n + 1];
263        let mut parents = linalg::etree::ParentsOwned::new(n);
264        let mut l_nz = vec![I::zero(); n];
265        let mut flag_workspace = vec![I::zero(); n];
266        ldl_symbolic(
267            mat,
268            &perm,
269            &mut l_colptr,
270            parents.view_mut(),
271            &mut l_nz,
272            &mut flag_workspace,
273            check_symmetry,
274        );
275
276        Self {
277            colptr: l_colptr,
278            parents,
279            nz: l_nz,
280            flag_workspace,
281            perm,
282        }
283    }
284
285    /// The size of the linear system associated with this decomposition
286    #[inline]
287    pub fn problem_size(&self) -> usize {
288        self.parents.nb_nodes()
289    }
290
291    /// The number of non-zero entries in L
292    #[inline]
293    pub fn nnz(&self) -> usize {
294        let n = self.problem_size();
295        self.colptr[n].index()
296    }
297
298    /// Compute the numerical decomposition of the given matrix.
299    pub fn factor<N>(
300        self,
301        mat: CsMatViewI<N, I>,
302    ) -> Result<LdlNumeric<N, I>, LinalgError>
303    where
304        N: Copy + Num + PartialOrd,
305    {
306        let n = self.problem_size();
307        let nnz = self.nnz();
308        let l_indices = vec![I::zero(); nnz];
309        let l_data = vec![N::zero(); nnz];
310        let diag = vec![N::zero(); n];
311        let y_workspace = vec![N::zero(); n];
312        let pattern_workspace = DStack::with_capacity(n);
313        let mut ldl_numeric = LdlNumeric {
314            symbolic: self,
315            l_indices,
316            l_data,
317            diag,
318            y_workspace,
319            pattern_workspace,
320        };
321        ldl_numeric.update(mat).map(|_| ldl_numeric)
322    }
323}
324
325impl<N, I: SpIndex> LdlNumeric<N, I> {
326    /// Compute the numeric LDLT decomposition of the given matrix.
327    ///
328    /// # Panics
329    ///
330    /// * if mat is not symmetric
331    pub fn new(mat: CsMatViewI<N, I>) -> Result<Self, LinalgError>
332    where
333        N: Copy + Num + PartialOrd,
334    {
335        let symbolic = LdlSymbolic::new(mat.view());
336        symbolic.factor(mat)
337    }
338
339    /// Compute the numeric decomposition L D L^T = P^T A P
340    /// where P is a permutation matrix.
341    ///
342    /// Using a good permutation matrix can reduce the non-zero count in L,
343    /// thus making the decomposition and the solves faster.
344    ///
345    /// # Panics
346    ///
347    /// * if mat is not symmetric
348    pub fn new_perm(
349        mat: CsMatViewI<N, I>,
350        perm: PermOwnedI<I>,
351        check_symmetry: SymmetryCheck,
352    ) -> Result<Self, LinalgError>
353    where
354        N: Copy + Num + PartialOrd,
355    {
356        let symbolic = LdlSymbolic::new_perm(mat.view(), perm, check_symmetry);
357        symbolic.factor(mat)
358    }
359
360    /// Update the decomposition with the given matrix. The matrix must
361    /// have the same non-zero pattern as the original matrix, otherwise
362    /// the result is unspecified.
363    pub fn update(&mut self, mat: CsMatViewI<N, I>) -> Result<(), LinalgError>
364    where
365        N: Copy + Num + PartialOrd,
366    {
367        ldl_numeric(
368            mat.view(),
369            &self.symbolic.colptr,
370            self.symbolic.parents.view(),
371            &self.symbolic.perm,
372            &mut self.symbolic.nz,
373            &mut self.l_indices,
374            &mut self.l_data,
375            &mut self.diag,
376            &mut self.y_workspace,
377            &mut self.pattern_workspace,
378            &mut self.symbolic.flag_workspace,
379        )
380    }
381
382    /// Solve the system A x = rhs
383    ///
384    /// The type constraints look complicated, but they simply mean that
385    /// `rhs` should be interpretable as a dense vector, and we will return
386    /// a dense vector of a compatible type (but owned).
387    pub fn solve<'a, V>(
388        &self,
389        rhs: V,
390    ) -> <<V as DenseVector>::Owned as DenseVector>::Owned
391    where
392        N: 'a + Copy + Num + std::ops::SubAssign + std::ops::DivAssign,
393        N: for<'r> std::ops::DivAssign<&'r N>,
394        V: DenseVector<Scalar = N>,
395        <V as DenseVector>::Owned: DenseVectorMut + DenseVector<Scalar = N>,
396        for<'b> &'b <V as DenseVector>::Owned: DenseVector<Scalar = N>,
397        for<'b> &'b mut <V as DenseVector>::Owned:
398            DenseVectorMut + DenseVector<Scalar = N>,
399        <<V as DenseVector>::Owned as DenseVector>::Owned:
400            DenseVectorMut + DenseVector<Scalar = N>,
401    {
402        let mut x = &self.symbolic.perm * rhs;
403        let l = self.l();
404        ldl_lsolve(&l, &mut x);
405        linalg::diag_solve(&self.diag, &mut x);
406        ldl_ltsolve(&l, &mut x);
407        let pinv = self.symbolic.perm.inv();
408        &pinv * x
409    }
410
411    /// The diagonal factor D of the LDL^T decomposition
412    pub fn d(&self) -> &[N] {
413        &self.diag[..]
414    }
415
416    /// The L factor of the LDL^T decomposition
417    pub fn l(&self) -> CsMatViewI<N, I> {
418        use std::slice::from_raw_parts;
419        let n = self.symbolic.problem_size();
420        // CsMat invariants are guaranteed by the LDL algorithm
421        unsafe {
422            let indptr = from_raw_parts(self.symbolic.colptr.as_ptr(), n + 1);
423            let nnz = indptr[n].index();
424            let indices = from_raw_parts(self.l_indices.as_ptr(), nnz);
425            let data = from_raw_parts(self.l_data.as_ptr(), nnz);
426            CsMatViewI::new_unchecked(sprs::CSC, (n, n), indptr, indices, data)
427        }
428    }
429
430    /// The size of the linear system associated with this decomposition
431    #[inline]
432    pub fn problem_size(&self) -> usize {
433        self.symbolic.problem_size()
434    }
435
436    /// The number of non-zero entries in L
437    #[inline]
438    pub fn nnz(&self) -> usize {
439        self.symbolic.nnz()
440    }
441}
442
443/// Perform a symbolic LDLT decomposition of a symmetric sparse matrix
444pub fn ldl_symbolic<N, I, PStorage>(
445    mat: CsMatViewI<N, I>,
446    perm: &Permutation<I, PStorage>,
447    l_colptr: &mut [I],
448    mut parents: linalg::etree::ParentsViewMut,
449    l_nz: &mut [I],
450    flag_workspace: &mut [I],
451    check_symmetry: SymmetryCheck,
452) where
453    N: Clone + Copy + PartialEq,
454    I: SpIndex,
455    PStorage: Deref<Target = [I]>,
456{
457    match check_symmetry {
458        SymmetryCheck::DontCheckSymmetry => (),
459        SymmetryCheck::CheckSymmetry => {
460            if !is_symmetric(&mat) {
461                panic!("Matrix is not symmetric")
462            }
463        }
464    }
465
466    let n = mat.rows();
467
468    let outer_it = mat.outer_iterator_papt(perm.view());
469    // compute the elimination tree of L
470    for (k, (_, vec)) in outer_it.enumerate() {
471        flag_workspace[k] = I::from_usize(k); // this node is visited
472        parents.set_root(k);
473        l_nz[k] = I::zero();
474
475        for (inner_ind, _) in vec.iter_perm(perm.inv()) {
476            let mut i = inner_ind;
477
478            if i < k {
479                while flag_workspace[i].index() != k {
480                    parents.uproot(i, k);
481                    l_nz[i] += I::one();
482                    flag_workspace[i] = I::from_usize(k);
483                    i = parents.get_parent(i).expect("uprooted so not a root");
484                }
485            }
486        }
487    }
488
489    let mut prev = I::zero();
490    for (k, colptr) in (0..n).zip(l_colptr.iter_mut()) {
491        *colptr = prev;
492        prev += l_nz[k];
493    }
494    l_colptr[n] = prev;
495}
496
497/// Perform numeric LDLT decomposition
498///
499/// `pattern_workspace` is a [`DStack`](DStack) of capacity n
500#[allow(clippy::too_many_arguments)]
501pub fn ldl_numeric<N, I, PStorage>(
502    mat: CsMatViewI<N, I>,
503    l_colptr: &[I],
504    parents: linalg::etree::ParentsView,
505    perm: &Permutation<I, PStorage>,
506    l_nz: &mut [I],
507    l_indices: &mut [I],
508    l_data: &mut [N],
509    diag: &mut [N],
510    y_workspace: &mut [N],
511    pattern_workspace: &mut DStack<I>,
512    flag_workspace: &mut [I],
513) -> Result<(), LinalgError>
514where
515    N: Clone + Copy + PartialEq + Num + PartialOrd,
516    I: SpIndex,
517    PStorage: Deref<Target = [I]>,
518{
519    assert!(y_workspace.len() == mat.outer_dims());
520    assert!(diag.len() == mat.outer_dims());
521    let outer_it = mat.outer_iterator_papt(perm.view());
522    for (k, (_, vec)) in outer_it.enumerate() {
523        // compute the nonzero pattern of the kth row of L
524        // in topological order
525
526        flag_workspace[k] = I::from_usize(k); // this node is visited
527        y_workspace[k] = N::zero();
528        l_nz[k] = I::zero();
529        pattern_workspace.clear_right();
530
531        for (inner_ind, &val) in
532            vec.iter_perm(perm.inv()).filter(|&(i, _)| i <= k)
533        {
534            y_workspace[inner_ind] = y_workspace[inner_ind] + val;
535            let mut i = inner_ind;
536            pattern_workspace.clear_left();
537            while flag_workspace[i].index_unchecked() != k {
538                pattern_workspace.push_left(I::from_usize(i));
539                flag_workspace[i] = I::from_usize(k);
540                i = parents.get_parent(i).expect("enforced by ldl_symbolic");
541            }
542            pattern_workspace.push_left_on_right();
543        }
544
545        // use a sparse triangular solve to compute the values
546        // of the kth row of L
547        diag[k] = y_workspace[k];
548        y_workspace[k] = N::zero();
549        #[allow(unused_labels)]
550        'pattern: for &i in pattern_workspace.iter_right() {
551            let i = i.index_unchecked();
552            let yi = y_workspace[i];
553            y_workspace[i] = N::zero();
554            let p2 = (l_colptr[i] + l_nz[i]).index();
555            let r0 = l_colptr[i].index()..p2;
556            let r1 = l_colptr[i].index()..p2; // Hack because not Copy
557            for (y_index, lx) in l_indices[r0].iter().zip(l_data[r1].iter()) {
558                // we cannot go inside this loop before something has actually
559                // be written into l_indices[l_colptr[i]..p2] so this
560                // read is actually not into garbage
561                // actually each iteration of the 'pattern loop adds writes the
562                // value in l_indices that will be read on the next iteration
563                // TODO: can some design change make this fact more obvious?
564                // This means we always know it will fit in an usize
565                let y_index = y_index.index_unchecked();
566                // Safety: `y_index` can take the values taken by `k`, so
567                // it is in `0..mat.outer_dims()`, and we have asserted
568                // that `y_workspace.len() == mat.outer_dims()`.
569                unsafe {
570                    let yw = y_workspace.get_unchecked_mut(y_index);
571                    *yw = *yw - *lx * yi;
572                }
573            }
574            // Safety: i and k are <= `mat.outer_dims()` and we have asserted
575            // that `diag.len() == mat.outer_dims()`.
576            let di = *unsafe { diag.get_unchecked(i) };
577            let dk = unsafe { diag.get_unchecked_mut(k) };
578            let l_ki = yi / di;
579            *dk = *dk - l_ki * yi;
580            l_indices[p2] = I::from_usize(k);
581            l_data[p2] = l_ki;
582            l_nz[i] += I::one();
583        }
584        if diag[k] == N::zero() {
585            return Err(LinalgError::SingularMatrix(SingularMatrixInfo {
586                index: k,
587                reason: "diagonal element is a numeric 0",
588            }));
589        }
590    }
591    Ok(())
592}
593
594/// Triangular solve specialized on lower triangular matrices
595/// produced by ldlt (diagonal terms are omitted and assumed to be 1).
596pub fn ldl_lsolve<N, I, V>(l: &CsMatViewI<N, I>, mut x: V)
597where
598    N: Clone + Copy + Num + std::ops::SubAssign,
599    I: SpIndex,
600    V: DenseVectorMut + DenseVector<Scalar = N>,
601{
602    for (col_ind, vec) in l.outer_iterator().enumerate() {
603        let x_col = *x.index(col_ind);
604        for (row_ind, &value) in vec.iter() {
605            *x.index_mut(row_ind) -= value * x_col;
606        }
607    }
608}
609
610/// Triangular transposed solve specialized on lower triangular matrices
611/// produced by ldlt (diagonal terms are omitted and assumed to be 1).
612pub fn ldl_ltsolve<N, I, V>(l: &CsMatViewI<N, I>, mut x: V)
613where
614    N: Clone + Copy + Num + std::ops::SubAssign,
615    I: SpIndex,
616    V: DenseVectorMut + DenseVector<Scalar = N>,
617{
618    for (outer_ind, vec) in l.outer_iterator().enumerate().rev() {
619        let mut x_outer = *x.index(outer_ind);
620        for (inner_ind, &value) in vec.iter() {
621            x_outer -= value * *x.index(inner_ind);
622        }
623        *x.index_mut(outer_ind) = x_outer;
624    }
625}
626
627#[cfg(test)]
628mod test {
629    use super::SymmetryCheck;
630    use sprs::stack::DStack;
631    use sprs::{self, linalg, CsMat, CsMatView, Permutation};
632
633    fn test_mat1() -> CsMat<f64> {
634        let indptr = vec![0, 2, 5, 6, 7, 13, 14, 17, 20, 24, 28];
635        let indices = vec![
636            0, 8, 1, 4, 9, 2, 3, 1, 4, 6, 7, 8, 9, 5, 4, 6, 9, 4, 7, 8, 0, 4,
637            7, 8, 1, 4, 6, 9,
638        ];
639        let data = vec![
640            1.7, 0.13, 1., 0.02, 0.01, 1.5, 1.1, 0.02, 2.6, 0.16, 0.09, 0.52,
641            0.53, 1.2, 0.16, 1.3, 0.56, 0.09, 1.6, 0.11, 0.13, 0.52, 0.11, 1.4,
642            0.01, 0.53, 0.56, 3.1,
643        ];
644        CsMat::new_csc((10, 10), indptr, indices, data)
645    }
646
647    fn test_vec1() -> Vec<f64> {
648        vec![
649            0.287, 0.22, 0.45, 0.44, 2.486, 0.72, 1.55, 1.424, 1.621, 3.759,
650        ]
651    }
652
653    fn expected_factors1() -> (Vec<usize>, Vec<usize>, Vec<f64>, Vec<f64>) {
654        let expected_lp = vec![0, 1, 3, 3, 3, 7, 7, 10, 12, 13, 13];
655        let expected_li = vec![8, 4, 9, 6, 7, 8, 9, 7, 8, 9, 8, 9, 9];
656        let expected_lx = vec![
657            0.076470588235294124,
658            0.02,
659            0.01,
660            0.061547930450838589,
661            0.034620710878596701,
662            0.20003077396522542,
663            0.20380058470533929,
664            -0.0042935346524025902,
665            -0.024807089102770519,
666            0.40878266366119237,
667            0.05752526570865537,
668            -0.010068305077340346,
669            -0.071852278207562709,
670        ];
671        let expected_d = vec![
672            1.7,
673            1.,
674            1.5,
675            1.1000000000000001,
676            2.5996000000000001,
677            1.2,
678            1.290152331127866,
679            1.5968603527854308,
680            1.2799646117414738,
681            2.7695677698030283,
682        ];
683        (expected_lp, expected_li, expected_lx, expected_d)
684    }
685
686    fn expected_lsolve_res1() -> Vec<f64> {
687        vec![
688            0.28699999999999998,
689            0.22,
690            0.45000000000000001,
691            0.44,
692            2.4816000000000003,
693            0.71999999999999997,
694            1.3972626557931991,
695            1.3440844395148306,
696            1.0599997771886431,
697            2.7695677698030279,
698        ]
699    }
700
701    fn expected_dsolve_res1() -> Vec<f64> {
702        vec![
703            0.16882352941176471,
704            0.22,
705            0.29999999999999999,
706            0.39999999999999997,
707            0.95460840129250657,
708            0.59999999999999998,
709            1.0830214557467768,
710            0.84170443406044937,
711            0.82814772179243734,
712            0.99999999999999989,
713        ]
714    }
715
716    fn expected_res1() -> Vec<f64> {
717        vec![
718            0.099999999999999992,
719            0.19999999999999998,
720            0.29999999999999999,
721            0.39999999999999997,
722            0.5,
723            0.59999999999999998,
724            0.70000000000000007,
725            0.79999999999999993,
726            0.90000000000000002,
727            0.99999999999999989,
728        ]
729    }
730
731    #[test]
732    fn test_factor1() {
733        let mut l_colptr = [0; 11];
734        let mut parents = linalg::etree::ParentsOwned::new(10);
735        let mut l_nz = [0; 10];
736        let mut flag_workspace = [0; 10];
737        let perm: Permutation<usize, &[usize]> = Permutation::identity(10);
738        let mat = test_mat1();
739        super::ldl_symbolic(
740            mat.view(),
741            &perm,
742            &mut l_colptr,
743            parents.view_mut(),
744            &mut l_nz,
745            &mut flag_workspace,
746            SymmetryCheck::CheckSymmetry,
747        );
748
749        let nnz = l_colptr[10];
750        let mut l_indices = vec![0; nnz];
751        let mut l_data = vec![0.; nnz];
752        let mut diag = [0.; 10];
753        let mut y_workspace = [0.; 10];
754        let mut pattern_workspace = DStack::with_capacity(10);
755        super::ldl_numeric(
756            mat.view(),
757            &l_colptr,
758            parents.view(),
759            &perm,
760            &mut l_nz,
761            &mut l_indices,
762            &mut l_data,
763            &mut diag,
764            &mut y_workspace,
765            &mut pattern_workspace,
766            &mut flag_workspace,
767        )
768        .unwrap();
769
770        let (expected_lp, expected_li, expected_lx, expected_d) =
771            expected_factors1();
772
773        assert_eq!(&l_colptr, &expected_lp[..]);
774        assert_eq!(&l_indices, &expected_li);
775        assert_eq!(&l_data, &expected_lx);
776        assert_eq!(&diag, &expected_d[..]);
777    }
778
779    #[test]
780    fn test_solve1() {
781        let (expected_lp, expected_li, expected_lx, expected_d) =
782            expected_factors1();
783        let b = test_vec1();
784        let mut x = b.clone();
785        let n = b.len();
786        let l = CsMatView::new_csc(
787            (n, n),
788            &expected_lp,
789            &expected_li,
790            &expected_lx,
791        );
792        super::ldl_lsolve(&l, &mut x);
793        assert_eq!(&x, &expected_lsolve_res1());
794        linalg::diag_solve(&expected_d, &mut x);
795        assert_eq!(&x, &expected_dsolve_res1());
796        super::ldl_ltsolve(&l, &mut x);
797
798        let x0 = expected_res1();
799        assert_eq!(x, x0);
800    }
801
802    #[test]
803    fn test_factor_solve1() {
804        let mat = test_mat1();
805        let b = test_vec1();
806        let ldlt = super::LdlNumeric::new(mat.view()).unwrap();
807        let x = ldlt.solve(&b);
808        let x0 = expected_res1();
809        assert_eq!(x, x0);
810    }
811
812    #[test]
813    fn permuted_ldl_solve() {
814        // |1      | |1      | |1     2|   |1      | |1      2| |1      |
815        // |  1    | |  2    | |  1 3  |   |    1  | |  21 6  | |    1  |
816        // |  3 1  | |    3  | |    1  | = |  1    | |   6 2  | |  1    |
817        // |2     1| |      4| |      1|   |      1| |2      8| |      1|
818        //     L         D        L^T    =     P          A        P^T
819        //
820        // |1      2| |1|   | 9|
821        // |  21 6  | |2|   |60|
822        // |   6 2  | |3| = |18|
823        // |2      8| |4|   |34|
824
825        let mat = CsMat::new_csc(
826            (4, 4),
827            vec![0, 2, 4, 6, 8],
828            vec![0, 3, 1, 2, 1, 2, 0, 3],
829            vec![1, 2, 21, 6, 6, 2, 2, 8],
830        );
831
832        let perm = Permutation::new(vec![0, 2, 1, 3]);
833
834        let ldlt = super::LdlNumeric::new_perm(
835            mat.view(),
836            perm,
837            super::SymmetryCheck::CheckSymmetry,
838        )
839        .unwrap();
840        let b = vec![9, 60, 18, 34];
841        let x0 = vec![1, 2, 3, 4];
842        let x = ldlt.solve(&b);
843        assert_eq!(x, x0);
844    }
845
846    #[test]
847    fn cuthill_ldl_solve() {
848        let mat = CsMat::new_csc(
849            (4, 4),
850            vec![0, 2, 4, 6, 8],
851            vec![0, 3, 1, 2, 1, 2, 0, 3],
852            vec![1., 2., 21., 6., 6., 2., 2., 8.],
853        );
854
855        let b = ndarray::arr1(&[9., 60., 18., 34.]);
856        let x0 = ndarray::arr1(&[1., 2., 3., 4.]);
857
858        let ldlt = super::Ldl::new()
859            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
860            .fill_in_reduction(super::FillInReduction::ReverseCuthillMcKee)
861            .numeric(mat.view())
862            .unwrap();
863        let x = ldlt.solve(b.view());
864        assert_eq!(x, x0);
865    }
866
867    #[cfg(feature = "sprs_suitesparse_ldl")]
868    #[test]
869    fn cuthill_ldl_solve_c() {
870        let mat = CsMat::new_csc(
871            (4, 4),
872            vec![0, 2, 4, 6, 8],
873            vec![0, 3, 1, 2, 1, 2, 0, 3],
874            vec![1., 2., 21., 6., 6., 2., 2., 8.],
875        );
876
877        let b = vec![9., 60., 18., 34.];
878        let x0 = vec![1., 2., 3., 4.];
879
880        let ldlt = super::Ldl::new()
881            .check_perm(super::PermutationCheck::CheckPerm)
882            .fill_in_reduction(super::FillInReduction::ReverseCuthillMcKee)
883            .numeric_c(mat.view())
884            .unwrap();
885        let x = ldlt.solve(&b);
886        assert_eq!(x, x0);
887    }
888
889    #[cfg(feature = "sprs_suitesparse_camd")]
890    #[test]
891    fn camd_ldl_solve() {
892        // 0 - A - 2 - 3
893        // | \ | \ | / |
894        // 7 - 5 - 6 - 4
895        // | / | / | \ |
896        // 8 - 9 - 1 - E
897        #[rustfmt::skip]
898        let triangles = ndarray::arr2(
899            &[[0, 7, 5],
900              [0, 5, 10],
901              [10, 5, 6],
902              [10, 6, 2],
903              [2, 6, 3],
904              [3, 6, 4],
905              [7, 8, 5],
906              [5, 8, 9],
907              [5, 9, 6],
908              [6, 9, 1],
909              [6, 1, 11],
910              [6, 11, 4]],
911        );
912        let lap_mat =
913            sprs::special_mats::tri_mesh_graph_laplacian(12, triangles.view());
914        let ldlt_camd = super::Ldl::new()
915            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
916            .fill_in_reduction(super::FillInReduction::CAMDSuiteSparse)
917            .numeric(lap_mat.view())
918            .unwrap();
919        let ldlt_cuthill = super::Ldl::new()
920            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
921            .fill_in_reduction(super::FillInReduction::ReverseCuthillMcKee)
922            .numeric(lap_mat.view())
923            .unwrap();
924        let ldlt_raw = super::Ldl::new()
925            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
926            .fill_in_reduction(super::FillInReduction::NoReduction)
927            .numeric(lap_mat.view())
928            .unwrap();
929        assert!(ldlt_camd.nnz() < ldlt_raw.nnz());
930        assert!(ldlt_camd.nnz() < ldlt_cuthill.nnz());
931    }
932}