Skip to content

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

Merged
merged 3 commits into from
Sep 5, 2019

Conversation

nrontsis
Copy link
Member

@nrontsis nrontsis commented Sep 3, 2019

Uses pivoted Cholesky 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]

@codecov-io
Copy link

codecov-io commented Sep 3, 2019

Codecov Report

Merging #94 into master will increase coverage by 0.34%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
src/algebra.jl 98.83% <100%> (+0.11%) ⬆️
src/convexset.jl 94.25% <100%> (-0.52%) ⬇️
src/infeasibility.jl 100% <100%> (ø) ⬆️
src/solver.jl 98.66% <0%> (+0.01%) ⬆️
src/chordal_decomposition.jl 93.63% <0%> (+1.83%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 340ecde...4b8396d. Read the comment docs.

@nrontsis
Copy link
Member Author

nrontsis commented Sep 4, 2019

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

nrontsis and others added 2 commits September 5, 2019 13:39
- Do cholesky decomposition instead of eigenvalue decomposition
- Squashed 4 commits
- Add in_place version of is_neg_def()
@migarstka
Copy link
Member

migarstka commented Sep 5, 2019

Thanks Nikitas. I squashed your commits into one and added some changes.

  • I got an error for the sparse version of is_pos_def, saying Unsupported keyword split=. For now the method is only used in the infeasibility checks anyway and the input variable will be dense. I removed the sparse version for now.

  • I added an in-place version of is_neg_def

  • Your proposed methods pass some tests that I wrote:

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
  • Furthermore, your version is a lot faster and the in-place version allocates less memory than the current one:
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 ) 🥉
  • Currently, we are using non-modifying versions of the functions is_pos_def and is_neg_def and they are only used in the infeasibility detection routines:
    • is_primal_infeasible -> support_function -> in_dual -> is_pos_def
    • is_dual_infeasible -> in_pol_recc -> is_neg_def
      However, since the functions operate on temporary variables unrelated to the ADMM loop iterates, we can use the faster in-place versions is_pos_def! and is_neg_def. I will do that in a separate commit.

@nrontsis
Copy link
Member Author

nrontsis commented Sep 5, 2019

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
@migarstka migarstka merged commit 020ada4 into master Sep 5, 2019
@migarstka migarstka deleted the check_psd_clean branch September 5, 2019 17:35
migarstka added a commit that referenced this pull request Sep 10, 2019
- Fixes a bug in in_dual(DualExpCone)
- Add primal infeasible DualExpCone problem to unit tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants