-
Notifications
You must be signed in to change notification settings - Fork 41
Avoid eigendecomposition in check for positive semidefiniteness #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report
@@ Coverage Diff @@
## master #94 +/- ##
==========================================
+ Coverage 89.89% 90.23% +0.34%
==========================================
Files 22 22
Lines 1712 1803 +91
==========================================
+ Hits 1539 1627 +88
- Misses 173 176 +3
Continue to review full report at Codecov.
|
After reading LAPACK Working Note #161 (Section 7) I think that using pivoting in Cholesky does not help with determining indefiniteness. Pivoting is useful when we know (a priori) that the matrix is positive semidefinite, and we want e.g. to estimate its rank. The non-pivoted Cholesky decomposition fails "iff the matrix is positive definite". I think this covers our needs because we always check definiteness with tolerances much higher than machine precision. However, to better emphasise the fact that we cannot (always) check if a matrix is positive semidefinite, I renamed the functions to: is_pos_sem_def -> is_pos_def
is_neg_sem_def -> is_neg_def |
- Do cholesky decomposition instead of eigenvalue decomposition - Squashed 4 commits
- Add in_place version of is_neg_def()
e8606ba
to
5565fa9
Compare
Thanks Nikitas. I squashed your commits into one and added some changes.
using BenchmarkTools, Random, SparseArrays, LinearAlgebra, COSMO, Test
rng = Random.MersenneTwister(213213);
# utility function to generate matrix with specific eigenvalues
function generate_matrix(dim::Int64, rng, eigs)
X = rand(rng, dim, dim)
Q, R = qr(X)
X = Q*Diagonal(eigs)*Q'
X = 0.5*(X+X')
return X
end
# current implementation
function is_pos_sem_def_old!(X, tol)
# set option 'N' to only compute eigenvalues, s is ordered from min to max
s, U = LAPACK.syevr!('N', 'A', 'U', X, 0.0, 0.0, 0, 0, -1.0);
return s[1] >= -tol
end
dim = 100
tol = 1e-4
######
# Unit tests
######
# negative definite matrix
eigs = -100 .+ rand(rng, dim) .* (-1 - (-100));
Xn = generate_matrix(dim, rng, eigs);
@test COSMO.is_pos_def(Xn, tol) == false
# positive definite matrix
@. eigs *= -1;
Xp = generate_matrix(dim, rng, eigs);
@test COSMO.is_pos_def(Xp, tol) == true
# positive semi-definite matrix (edge case negative)
eigs[1] = -1e-5;
Xe1 = generate_matrix(dim, rng, eigs);
@test COSMO.is_pos_def(Xe1, tol) == true
dim = 400
Xtest = generate_matrix(dim, rng, rand(rng, dim));
Xtest2 = copy(Xtest);
Xtest3 = copy(Xtest);
# with copy
println("With copy of matrix:")
@btime COSMO.is_pos_def($Xtest, 1e-4)
# without allocation
println("In place version:")
@btime COSMO.is_pos_def!($Xtest2, 1e-4)
@btime is_pos_sem_def_old!($Xtest3, 1e-4) Benchmarking yields the following results: 617.922 μs (6 allocations: 1.22 MiB) ( for COSMO.is_pos_def ) 🥈
500.471 ns (4 allocations: 112 bytes) ( for COSMO.is_pos_def! ) 🥇
6.431 ms (10 allocations: 147.75 KiB) ( for current implementation ) 🥉
|
Great, thanks for the detailed feedback! |
- This involves renaming support_function, in_dual, in_pol_recc to reflect the in-place character if a PsdCone or PsdConeTriangle is present in the problem - Add an in-place lmul!(L, x) method for the case L:Diagonal, x:Vector - Add unit tests for lmul!(L, x) - Improve performance of infeasibility detection routines
- Fixes a bug in in_dual(DualExpCone) - Add primal infeasible DualExpCone problem to unit tests
Uses
pivotedCholesky instead of eigendecomposition for checking positive/negative semidefiniteness. Advantages of the approach include that Cholesky is faster than eigen-decomposition, scales much better with parallelism and can exploit sparsity.The positive semidefiniteness checks can be become computationally relevant when using approximate projections, as, in this case, we are trying to avoid big eigendecompositions as much as possible.
References [1]
[2] [3]