Closed fda5796b-398e-41cd-914c-35e067c67f51 closed 2 years ago
I have a solution implemented at https://github.com/bollu/sage/tree/u/gh-bollu/jan-24-cholesky-for-sparse-numerical-matrices.
Author: Siddharth Bhat
New commits:
4351684 | Trac #13674: implement Chokesly factorization for sparse numerical matrices |
Things I don't understand very well:
cvxopt
? The best idea I had was to use cvxopt.matrix(sagemat.to_numpy())
; perhaps there are faster methods I am unaware of?float
. What is the correct way to hand the matrix entries off to cvxopt
?Reviewer: Dima Pasechnik
Ohhkay. So, the title of this ticket is a bit misleading. It's not a cholesky for sparse matrices that's missing, but rather a cholesky decomposition for matrices over inexact rings. For example,
sage: A = matrix(RR, [[1, 1], [1, 2]])
sage: A.cholesky()
...
TypeError: base ring of the matrix must be exact, not Real Field with 53 bits of precision
The use of RealField(100)
or any similar ring produces the same result. The problem only appears related to sparse matrices because there is a special case implemented for dense matrices over RDF
, but not sparse matrices over RDF
. Thus, over RDF
, the sparse implementation is indeed just "missing" (it could use the dense implementation without hurting anything).
But regardless, the real problem to be solved here is that we need a numerically-stable cholesky factorization that works for most inexact rings. That implementation can then be used (in the meantime) for both the dense and sparse methods, until someone decides to come along and speed it up in the sparse case. I've essentially already done this on https://github.com/sagemath/sage-prod/issues/10332, which adds a numerically-stable block_ldlt()
method for all Hermitian matrices. When your matrix is positive-definite, the block-LDLT factorization can be turned into a cholesky factorization after taking the square root of D
.
So my suggestion for this ticket is to use block_ldlt()
from the other ticket instead of adding a special case that relies on cvxopt
. By using block_ldlt()
, you allow the cholesky()
method to work on fields like RR
that cvxopt
doesn't know about. It should also simplify the code a little bit, at the price of some administrative overhead: you'll have to,
block_ldlt()
instead of calling cvxopt.I think (3) is pretty self-explanatory after reading the docs for block_ldlt()
, but if not, I'm happy to help. E.g.
sage: A = matrix(QQ, [[9, 0, 3], [0, 1, 0], [3, 0, 25/4]])
sage: A.is_positive_definite()
True
sage: P,L,D = A.block_ldlt()
sage: L*D.apply_map(sqrt)
[ 3 0 0]
[ 0 1 0]
[ 1 0 1/2*sqrt(21)]
sage: A.cholesky()
[ 3 0 0]
[ 0 1 0]
[ 1 0 2.291287847477920?]
You might be able to save a few microseconds by using the internal _block_ldlt()
method instead of the nice public one as well.
Thanks a lot for the link! My only concern is with that of sparseness; the reason I choose to jump hoops with cvxopt
is for the performance wins for large sparse matrices, that I get from running discrete differential geometry algorithms such as geodesics in heat: https://arxiv.org/abs/1204.6216
I'll be glad to benchmark the code, and see which method is faster; I think the route you propose is performant for dense inexact matrices for sure. I'm unsure about the sparse case. Could you please shed some light on the performance characteristics of the linked implementation?
block_ldlt()
will be pretty slow on large sparse matrices since the implementation scans the matrix (without regard for sparsity) looking for pivots. It's main benefit (with respect to cholmod
) is that it would work for other fields, like sparse matrices over RR
.
If you need the performance for sparse matrices over RDF
then something like cholmod
is indeed the way to go. I'm surprised we don't have a special matrix subclass for sparse RDF
matrices already; my first attempt would be to override cholesky()
there, similar to how cholesky()
is overridden in matrix_double_dense.pyx
for dense matrices.
Indeed, for sparse matrices over RDF the cholmod interface of cvxopt is probably as fast as it can get (precision is of course another question).
In general, I'd be looking at wrapping up arb's cholesky and LDLs, as something more robust than a naive attempt to implement this stuff.
Now that I know you want it to be fast (as opposed to "just not crash"), here's my revised advice:
sage/matrix/matrix_real_double_sparse.pyx
, and override only the cholesky()
method there, with an implementation that uses cholmod
.src/sage/matrix/matrix_space.py
that tells the matrix constructor to use your new subclass when the ring is RDF
and sparse=True
. Something like,if R is sage.rings.real_double.RDF:
from . import matrix_real_double_sparse
return matrix_real_double_sparse.Matrix_real_double_sparse
block_ldlt()
.The subclass approach will avoid using cholmod
where it's inappropriate. If you're not familiar with cython, the syntax isn't that much different than plain python. Here's an example pyx and pxd file that creates a subclass.
src/sage/matrix/matrix_real_double_sparse.pxd:
from .matrix_generic_sparse cimport Matrix_generic_sparse
cdef class Matrix_real_double_sparse(Matrix_generic_sparse):
pass
src/sage/matrix/matrix_real_double_sparse.pyx:
from .matrix_generic_sparse cimport Matrix_generic_sparse
cdef class Matrix_real_double_sparse(Matrix_generic_sparse):
def cholesky(self):
print("DEBUG: subclass method")
return None
Replying to @dimpase:
In general, I'd be looking at wrapping up arb's cholesky and LDLs, as something more robust than a naive attempt to implement this stuff.
For things like cholesky over RR
this would likely be better than a naive implementation based on block_ldlt()
, but the big problem I was solving by reimplementing block-LDLT was to gain a factorization that works on indefinite matrices.
Setting new milestone based on a cursory review of ticket status, priority, and last modification date.
Ticket #31619 allows cholesky()
and is_positive_definite()
to work over inexact rings as promised, but it will be slower than necessary for sparse RDF matrices. A matrix subclass that delegates to cholmod()
in that case is the last missing piece.
Setting a new milestone for this ticket based on a cursory review.
cvxopt is now a pseudo-optional package, but we should still be able to use the approach in comment:14. We can super()
if cvxopt isn't available.
Changed author from Siddharth Bhat to Siddharth Bhat, Michael Orlitzky
This should work for both RDF
and CDF
, but it would be nice to have some more serious examples for test cases. The cvxopt interface is a little sketchy so I'd like to be sure that we're using it "correctly," insofar as is possible for an undocumented interface.
Changed branch from u/gh-bollu/jan-24-cholesky-for-sparse-numerical-matrices to u/mjo/ticket/13674
lgtm
Changed branch from u/mjo/ticket/13674 to 954b9ba
I am seeing failures such as the one below on aarch64:
sage -t --long --random-seed=138452687149883420730489596915102319785 /nix/store/1jyscb1slmz6134mlsfs9gfjs4kv8w8i-sage-src-9.5.beta8/src/sage/matrix/matrix_double_sparse.pyx
**********************************************************************
File "/nix/store/1jyscb1slmz6134mlsfs9gfjs4kv8w8i-sage-src-9.5.beta8/src/sage/matrix/matrix_double_sparse.pyx", line 95, in sage.matrix.matrix_double_sparse.Matrix_double_sparse.cholesky
Failed example:
L = A.cholesky()
Exception raised:
Traceback (most recent call last):
File "/nix/store/vwd2z6p52kzhidwwvwavgw9jxp1165qh-python3-3.9.6-env/lib/python3.9/site-packages/sage/doctest/forker.py", line 694, in _run
self.compile_and_execute(example, compiler, test.globs)
File "/nix/store/vwd2z6p52kzhidwwvwavgw9jxp1165qh-python3-3.9.6-env/lib/python3.9/site-packages/sage/doctest/forker.py", line 1096, in compile_and_execute
exec(compiled, globs)
File "<doctest sage.matrix.matrix_double_sparse.Matrix_double_sparse.cholesky[20]>", line 1, in <module>
L = A.cholesky()
File "sage/matrix/matrix_double_sparse.pyx", line 110, in sage.matrix.matrix_double_sparse.Matrix_double_sparse.cholesky (build/cythonized/sage/matrix/matrix_double_sparse.c:2820)
raise ValueError("matrix is not Hermitian")
ValueError: matrix is not Hermitian
@collares - please open a new ticket for this.
Opened #33023 for the test failure, thanks for letting me know about the correct procedure. I didn't know the "Commit" field would be cleared when I posted my first comment, sorry about that.
Replying to @collares:
Opened #33023 for the test failure, thanks for letting me know about the correct procedure. I didn't know the "Commit" field would be cleared when I posted my first comment, sorry about that.
Thanks, the commit field thing is no big deal, that always happens. I'll see if I can reproduce the problem. The matrix is Hermitian by construction (unless I've made some typo I can't see) so it should be interesting.
The Cholesky decomposition are implemented in the file sage/matrix/matrix2.pyx for some subfield of the algebraic numbers.
In this implementation the base ring must be exact or, for numerical work, a matrix with a base ring of
RDF
orCDF
must be used.For the numerical work it's used the Cholesky decomposition implemented in sage/matrix/matrix_double_dense.pyx and because of this a error raised when try to compute the numerical Cholesky decomposition of a sparse matrix.
For this solve this ticket the numerical sparce Cholesky decompostion need to be implemented.
For more information about this topic see https://groups.google.com/forum/?fromgroups=#!topic/sage-support/do55Fayur6U.
CC: @orlitzky @collares
Component: linear algebra
Keywords: matrix, decomposition, cholesky, sparse
Author: Siddharth Bhat, Michael Orlitzky
Branch:
954b9ba
Reviewer: Dima Pasechnik
Issue created by migration from https://trac.sagemath.org/ticket/13674