9.3 Computing a Sparse Cholesky Factorization¶
Given a positive semidefinite symmetric (PSD) matrix
it is well known there exists a matrix
If the matrix
A system of linear equations
can be solved by first solving the lower triangular system followed by the upper triangular system .A quadratic term
in a constraint or objective can be replaced with for , potentially leading to a more robust formulation (see [And13]).
Therefore, MOSEK provides a function that can compute a Cholesky factorization of a PSD matrix. In addition a function for solving linear systems with a nonsingular lower or upper triangular matrix is available.
In practice
However, if we symmetrically permute the rows and columns of
then the Cholesky factorization of
which is sparser than
Computing a permutation matrix that leads to the sparsest Cholesky factorization or the minimal amount of work is NP-hard. Good permutations can be chosen by using heuristics, such as the minimum degree heuristic and variants. The function Env.compute_sparse_cholesky
provided by MOSEK for computing a Cholesky factorization has a build in permutation aka. reordering heuristic. The following code illustrates the use of Env.compute_sparse_cholesky
and Env.sparse_triangular_solve_dense
.
here
to download.¶ mosek::compute_sparse_cholesky(0, //Mosek chooses number of threads
1, //Apply reordering heuristic
1.0e-14, //Singularity tolerance
&anzc, &aptrc, &asubc, &avalc,
& mut perm, & mut diag,
& mut lnzc, & mut lptrc, & mut lensubnval, & mut lsubc, & mut lvalc)?;
print_sparse(n, perm.as_slice(), diag.as_slice(), lnzc.as_slice(), lptrc.as_slice(), lsubc.as_slice(), lvalc.as_slice());
/* Permuted b is stored as x. */
let mut x : Vec<f64> = perm.iter().map(|&i| b[i as usize]).collect();
/*Compute inv(L)*x.*/
mosek::sparse_triangular_solve_dense(Transpose::NO, lnzc.as_slice(), lptrc.as_slice(), lsubc.as_slice(), lvalc.as_slice(), x.as_mut_slice())?;
/*Compute inv(L^T)*x.*/
mosek::sparse_triangular_solve_dense(Transpose::YES, lnzc.as_slice(), lptrc.as_slice(), lsubc.as_slice(), lvalc.as_slice(), x.as_mut_slice())?;
print!("\nSolution A x = b, x = [ {:?} ]",
iproduct!(0..n,izip!(perm.iter(),x.iter()))
.filter_map(|(i,(&pj,&xj))|if pj as usize == i { Some(xj) } else { None })
.collect::<Vec<f64>>());
We can set up the data to recreate the matrix
//Observe that anzc, aptrc, asubc and avalc only specify the lower triangular part.
let n = 4;
let anzc = [4, 1, 1, 1];
let asubc = [0, 1, 2, 3, 1, 2, 3];
let aptrc = [0, 4, 5, 6];
let avalc = [4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
let b = [13.0, 3.0, 4.0, 5.0];
and we obtain the following output:
Example with positive definite A.
P = [ 3 2 0 1 ]
diag(D) = [ 0.00 0.00 0.00 0.00 ]
L=
1.00 0.00 0.00 0.00
0.00 1.00 0.00 0.00
1.00 1.00 1.41 0.00
0.00 0.00 0.71 0.71
Solution A x = b, x = [ 1.00 2.00 3.00 4.00 ]
The output indicates that with the permutation matrix
there is a Cholesky factorization
The remaining part of the code solvers the linear system
The second example shows what happens when we compute a sparse Cholesky factorization of a singular matrix. In this example
let n = 3;
let anzc = [3, 2, 1];
let asubc = [0, 1, 2, 1, 2, 2];
let aptrc = [0, 3, 5, ];
let avalc = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
Now we get the output
P = [ 0 2 1 ]
diag(D) = [ 0.00e+00 1.00e-14 1.00e-14 ]
L=
1.00e+00 0.00e+00 0.00e+00
1.00e+00 1.00e-07 0.00e+00
1.00e+00 0.00e+00 1.00e-07
which indicates the decomposition
where
Since Env.compute_sparse_cholesky
, in this case
where
We will end this section by a word of caution. Computing a Cholesky factorization of a matrix that is not of full rank and that is not suffciently well conditioned may lead to incorrect results i.e. a matrix that is indefinite may declared positive semidefinite and vice versa.