Skip to content
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

extend Cholesky decomposition method for finite fields #39200

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

jacksonwalters
Copy link
Contributor

@jacksonwalters jacksonwalters commented Dec 24, 2024

Given a Hermitian matrix $U$ over $GF(q^2)$, returns a matrix $A$ such that $U = AA^\ast$, where $^\ast$ denotes conjugate-transpose.

Note that the Cholesky decomposition is meant for characteristic zero, and also only returns a lower/upper triangular matrix.

📝 Checklist

  • The title is concise and informative.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation and checked the documentation preview.

⌛ Dependencies

Copy link

github-actions bot commented Dec 26, 2024

Documentation preview for this PR (built with commit ae8eab0; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

@jacksonwalters jacksonwalters closed this by deleting the head repository Dec 26, 2024
@jacksonwalters jacksonwalters force-pushed the hermitian_decomposition branch from caed579 to f2f35b7 Compare December 27, 2024 14:38
@tscrim
Copy link
Collaborator

tscrim commented Dec 30, 2024

I really dislike these apply-suggestion-commits with a completely useless commit message. Could you please squash and rebase? It makes it easier to see which changes have been done and looking through the logs much later.

@jacksonwalters jacksonwalters force-pushed the hermitian_decomposition branch from 6300bae to a8cd89e Compare December 30, 2024 00:54
@jacksonwalters
Copy link
Contributor Author

Sure. I agree, the commit messages are pretty useless. I've squashed the commits here. Which branch should I rebase on? I rebased PR#38455 on this one (PR#39200) already.

@tscrim
Copy link
Collaborator

tscrim commented Dec 30, 2024

Thanks. Sorry, I misspoke slightly, I meant force-push.You will need to rebase #38455 again though…

@jacksonwalters
Copy link
Contributor Author

@tscrim Okay, I have rebased #38455 on this again and attempted to resolve merge conflicts.

@jacksonwalters
Copy link
Contributor Author

I don't think this handles the 1x1 case, so I'll add a fix.

@jacksonwalters
Copy link
Contributor Author

@tscrim How do I squash these commits given there's a merge commit? We may've done this before, but I don't see how.

@tscrim
Copy link
Collaborator

tscrim commented Jan 4, 2025

I would just rebase the entire branch over the latest develop branch and squash commits as reasonable.

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more details, modulo making row 0-based.

row += 1

# Look for a non-zero element on the main diagonal, starting from `row`
i = row - 1 # Adjust for zero-based indexing in Sage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is possible as we mutate i compared to row.

@jacksonwalters
Copy link
Contributor Author

@tscrim I've refactored it so that row is initialized to -1, so that many row - 1 statements are just row. I agree, I don't think we can decouple it from i in any way.

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I look into the Cholesky decomposition and its doc, I am thinking we should actually call this method cholesky_extended() for "extended Cholesky decomposition". Or possibly accessing this via cholesky(extended=True) (with the default being False), in which case this method would be called something like _cholesky_extended_ff. I am actually leaning towards the latter right now since it is the most natural. Sorry for a somewhat big change so far along.

This paper is also somewhat relevant: journal version; [arXiv version](https://arxiv.org/abs/2202.04012v1.

@jacksonwalters
Copy link
Contributor Author

I'm happy to have this accessed via cholesky(extended=True). That's really how we came upon it, trying to do the Cholesky decomposition over finite fields but realizing it didn't work for some matrices. It also makes it more likely other people will see it and use it. I'll attempt to move it.

@jacksonwalters
Copy link
Contributor Author

Okay, I have moved the code into cholesky(). It may require some changes. I believe Dima and I did look at that paper when trying to figure this out, but it didn't have exactly this method. Dima found it in GAP.

@jacksonwalters jacksonwalters changed the title add hermitian_decomposition method extend Cholesky decomposition method for finite fields of square order Jan 18, 2025
@jacksonwalters jacksonwalters changed the title extend Cholesky decomposition method for finite fields of square order extend Cholesky decomposition method for finite fields Jan 18, 2025
@dimpase
Copy link
Member

dimpase commented Jan 30, 2025

please rebase over the latest beta (or merge it in)

@dimpase
Copy link
Member

dimpase commented Jan 30, 2025

otherwise, it's ready to go. CI failures (you can inspect what tests fail) are not relevant

@jacksonwalters
Copy link
Contributor Author

jacksonwalters commented Jan 30, 2025 via email

@jacksonwalters
Copy link
Contributor Author

jacksonwalters commented Jan 30, 2025 via email

@dimpase
Copy link
Member

dimpase commented Jan 30, 2025

to rebase a local branch foo42 over the current develop, do

git checkout foo42
git fetch origin develop
git rebase origin/develop 
# it might start asking you questions 

@tscrim
Copy link
Collaborator

tscrim commented Jan 31, 2025

I do something slightly different:

git checkout foo42
git fetch upstream develop:develop
git rebase develop
# deal with merge conflicts; "git rebase --continue" once resolved
git push -f origin foo42

@dimpase
Copy link
Member

dimpase commented Jan 31, 2025

you probably meant git rebase develop

@tscrim
Copy link
Collaborator

tscrim commented Jan 31, 2025

you probably meant git rebase develop

Yes, I did. Thanks. Fixed.

@jacksonwalters jacksonwalters force-pushed the hermitian_decomposition branch from 704ae31 to c59ff69 Compare February 26, 2025 21:45
@jacksonwalters
Copy link
Contributor Author

jacksonwalters commented Feb 26, 2025

@tscrim @dimpase Okay, I've rebased this over develop. Seems like there are still some issues with the CI tests, not sure how to resolve those.

@tscrim
Copy link
Collaborator

tscrim commented Feb 27, 2025

They don't look related and some of them are known to be flaky. I wouldn't worry about the failures.

@tscrim
Copy link
Collaborator

tscrim commented Feb 27, 2025

@jacksonwalters What about the last round of my suggested changes? Any questions/comments/disagreements?

@jacksonwalters
Copy link
Contributor Author

@jacksonwalters What about the last round of my suggested changes? Any questions/comments/disagreements?

My apologies, I think I saw those right as I was getting caught up with another project. Those have been addressed now. The docs for _cholesky_extended_ff() could probably be elaborated a bit, and just want to make sure having the input matrix be nonsingular is a necessary condition.

@jacksonwalters
Copy link
Contributor Author

They don't look related and some of them are known to be flaky. I wouldn't worry about the failures.

Yes, they seem to be flaky. I guess my concern is that this PR will not be resolved until those start passing. That could be indefinite it seems?

@tscrim
Copy link
Collaborator

tscrim commented Feb 27, 2025

They don't look related and some of them are known to be flaky. I wouldn't worry about the failures.

Yes, they seem to be flaky. I guess my concern is that this PR will not be resolved until those start passing. That could be indefinite it seems?

No, it will get merged even if the automated tests work. As long as the actual tests pass (when run by humans on more standard machines), then it will get merged.

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Just a few more little things. (It would be nice to squash some/all of the commits though since there are a lot of completely unhelpful commit messages...)

Comment on lines 12996 to 12997
if self.is_singular():
raise ValueError("matrix must be nonsingular")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.is_singular():
raise ValueError("matrix must be nonsingular")

I think this is somewhat expensive check that is unnecessary since it fails during the process. A division by 0 right? That would be the place to check (or perhaps catch the error).

Also, please add a doctest for the singular case (just in the helper method).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the thing, it still produces an output matrix, it just doesn't satisfy the B*B.H == U. For example, U = matrix(GF(3**2),[[1,4,7],[4,1,4],[7,4,1]]) yields B = matrix(GF(3**2),[[1 0 0],[1 1 0],[1 0 1]]). So we can only catch the error at the end, when it fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've added a (failing) singular example for now. I don't think there's a division by zero. It must be towards the end of the method where the rank is computed, and if it's not full rank it causes the issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above fail case is rank 1. The following fail case is rank 2: U = matrix(F,[[0,4,7],[4,1,4],[7,4,1]]). Note that at the end of the row reduction, we are already computing r, which is the rank of the input matrix. I propose we just check that the matrix is full rank, since it appears that it needs to be. Does it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it doesn't. The example U = matrix(GF(3**2),[[0,0,0],[0,1,0],[0,0,1]]) should just return the same matrix. It returns B = matrix(GF(3**2),[[0 0 1],[1 0 0],[0 1 0]]), which does not satisfy B*B.H == U.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like in the initial While loop, it will note the first diagonal entry is zero, then try to do row/col swaps to ensure it's nonzero. I think for rank deficient matrices there may just be no way to do row/col swaps to get all diagonal entries nonzero. So we're left with a "bad" matrix, when really we shouldn't've done any swaps at all.

@jacksonwalters jacksonwalters force-pushed the hermitian_decomposition branch from f24dfd7 to cefde12 Compare March 3, 2025 21:44
@jacksonwalters
Copy link
Contributor Author

@tscrim Squashed above commits. There are some singular cases it should handle, though. Namely, U = matrix(GF(3**2),[[0,0,0],[0,1,0],[0,0,1]]) is a rank two matrix, and the output should just be matrix(GF(3**2),[[0,0,0],[0,1,0],[0,0,1]]). Even if we allow the computation to proceed, we get the wrong result: B = matrix(GF(3**2),[[0 0 1],[1 0 0],[0 1 0]]).

add hermitian_decomposition method

given a Hermitian matrix U, returns a matrix A such that U = AA*

use translated GAP source code for BaseChangeCanonical

raise ValueError for non full rank matrices

this method does not work for singular matrices. since we are already computing the rank in the row reduction, we can just check at the end whether the rank is full, and if not exit with a ValueError. this may be overcautious - it's not clear that this won't work for some singular matrices. there are cases where it certainly doesn't work, and we include one in the doctest.

add example for singular case

this example is currently failing and needs to be caught

remove unnecessary import, singularity check

if the matrix `U` is singular, the process will still result in a matrix but it will fail to have the expected property, namely `B*B.H == U`.

Update src/sage/matrix/matrix2.pyx

avoid import, directly call `sqrt` method, don't extend to keep result in `ZZ`

fix example

this was using the output from the `forms` package, when it should be from the GAP source translation.

change examples

there is a failing example due to the fact that the matrix is singular with q=3, and U = matrix(F,[[1,4,7],[4,1,4],[7,4,1]]).

move _cholesky_extended_ff to hidden method

move _cholesky_extended_ff to a separate hidden method, and just call it from inside cholesky if extended=true. add doctests and docs for the method separately. sparse for now, should be expanded upon.

Update src/sage/matrix/matrix2.pyx

add output for GF(3**2) case

this case fails to have a lower triangular decomposition which has been checked by brute force.

Update src/sage/matrix/matrix2.pyx

move two examples over finite fields from tests

the two examples computing the extended Cholesky decomposition over square order finite fields are in TESTS, and should be moved to EXAMPLES.

Update src/sage/matrix/matrix2.pyx

this is better and clarifies that the result of the Cholesky decomposition is lower triangular (and not possibly upper triangular).

Update src/sage/matrix/matrix2.pyx

add single whitespace before `extended` parameter

Update src/sage/matrix/matrix2.pyx

looks good. makes sense to point out the Cholesky decomposition might not exist, given that was our initial difficulty.

3 trailing whitespace

move hermitian_decomposition into cholesky

this method for decomposition a Hermitian matrix U = AA* is similar in spirit to the Cholesky decomposition, but extends it to work over finite fields of square order.

add two examples

add two examples of a Hermitian decomposition, one of which is not upper/lower triangular (so would be impossible with Cholesky decomposition)

Update src/sage/matrix/matrix2.pyx

yes, just forgot to update this

initialize row to -1

Update src/sage/matrix/matrix2.pyx

add word "package" to GAP ``forms``

Update src/sage/matrix/matrix2.pyx

change \ast to * for consistency

Trigger CI tests

use self.__class__ within matrix.pyx file

Trigger CI tests

remove blank line

Update src/sage/matrix/matrix2.pyx

Update src/sage/matrix/matrix2.pyx

Update src/sage/matrix/matrix2.pyx

Update src/sage/matrix/matrix2.pyx

Update src/sage/matrix/matrix2.pyx

use Matrix

handle 1x1 case

format failing test case properly

format failing test case properly by removing variables that hold results of computation. just call the function that raises the exception

Co-Authored-By: Travis Scrimshaw <[email protected]>
@jacksonwalters jacksonwalters force-pushed the hermitian_decomposition branch from 5b519ea to 7c27ee4 Compare March 3, 2025 22:29
Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for everything. Two last little details to try and make it play better with the other methods.

jacksonwalters and others added 2 commits March 3, 2025 20:55
cache the rank of the matrix since are computing it anyways

Co-authored-by: Travis Scrimshaw <[email protected]>
fetch the cached rank. if it's full or not yet computed, continue, otherwise throw a ValueError that the matrix is not full rank.

Co-authored-by: Travis Scrimshaw <[email protected]>
@jacksonwalters
Copy link
Contributor Author

Thank you for everything. Two last little details to try and make it play better with the other methods.

Nice, I've committed those changes. At this point I'm happy to just have it work for full rank matrices (since that is all we need for 38455.

However, I'm wondering if setting D = identity_matrix(F,n) is part of the issue. For a rank deficient matrix of rank r < n, shouldn't we use a matrix with r one's on the diagonal instead?

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the mathematics off-hand. I'd say let's go with this for now, and we can come back to it later if there is a good use-case for it.

If the tests pass (morally), then I will set this to a positive review.

@jacksonwalters
Copy link
Contributor Author

jacksonwalters commented Mar 4, 2025

I'm not sure about the mathematics off-hand. I'd say let's go with this for now, and we can come back to it later if there is a good use-case for it.

If the tests pass (morally), then I will set this to a positive review.

Sounds good. I agree that it would be better to handle it in a separate PR if needed. I tried initializing D to have r one's on the diagonal, and it was still off, so it's something subtle with the swaps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants