-
Notifications
You must be signed in to change notification settings - Fork 73
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
Bivector and rotor splits for arbitrary GAs #398
base: master
Are you sure you want to change the base?
Conversation
This looks great, hopefully the inclusion of the general decomposition of bivectors means we can remove the various algebra specific bivector decompositions in other places in the code :) I'll have a dig for them later so we can do some spring cleaning |
@hugohadfield that sounds great! Cleaning up the code base is what GA is for ;). I have made one potentially contentious change to if abs(np.linalg.det(intermed)) < _settings._eps:
raise ValueError("multivector has no left-inverse") and instead will let numpy decide. Maybe there is a good reason this line was added that I'm not aware of, but in my experiments with bivector splits I've regularly walked into this condition whereas I knew the requested inversion was possible. Also decreasing |
This pull request introduces 1 alert when merging f4e56da into 31651d0 - view on LGTM.com new alerts:
|
@hugohadfield, when it comes to cleaning up the code base, should that be part of this PR or is it better to leave this one focused on adding the general solution and do the clean-up separately? |
Good question, I think the file that is likely to be affected most by this merge is:
There is also some functionality from @arsenovic here: clifford/clifford/tools/__init__.py Line 462 in 31651d0
And the exponential is chosen currently as the taylor series by default here: clifford/clifford/_multivector.py Line 101 in 31651d0
|
It looks to me like the linear algebra method for inverting multivectors is not happy with the complex numbers and we might have to merge this: #373 first in order to get an inverse that is happy |
Looking at the talking points on here, specifically:
How does this failure manifest itself? Like, does the algorithm just give the wrong result or do we get a divide by zero or something like that? Can we construct a test case that doesn't work? If so we should include some test cases to ensure that we handle these failures in a reasonable way :) I'm also tempted to build in something to test that exp and log definitely round trip for the principal logarithm of rotors in loads of different spaces |
Due to numerical errors, there are two possibilities. In theory the degenerate cases lead to a "no inverse exists" error, although in practice I've had instances where they are different enough for the code to just continue anyway, especially if the eigenvalues pick up a small imaginary part like lets say, I'll think about and implement a test case later.
I agree with that, I think it would be a great idea to make some tests to verify that R = exp(B)
assert R == exp(log(R)) if I understood you correctly. |
So if I create the known_split def r4_split():
alg = Layout([1, 1, 1, 1])
e1, e2, e3, e4 = alg.basis_vectors_lst
return {'B': 2*e1*e2 + 2*e3*e4,
'Bs': [2*e3*e4, 2*e1*e2],
'ls': [-4.0, -4.0],
'logR': 2*e1*e2 + 2*e3*e4} then clifford/test/test_invariant_decomposition.py:72 (TestInvariantDecomposition.test_known_splits[known_split0])
self = <clifford.test.test_invariant_decomposition.TestInvariantDecomposition object at 0x16e35fb20>
known_split = {'B': (2^e12) + (2^e34), 'Bs': [(2^e34), (2^e12)], 'logR': (2^e12) + (2^e34), 'ls': [-4.0, -4.0]}
@pytest.mark.parametrize('known_split', [r4_split()])
def test_known_splits(self, known_split):
B = known_split['B']
> Bs, ls = bivector_split(B, roots=True)
test/test_invariant_decomposition.py:76:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
invariant_decomposition.py:101: in bivector_split
Bs, ls = _bivector_split(Wm, return_all=False)
invariant_decomposition.py:82: in _bivector_split
Bs.append(single_split(Wm, li))
invariant_decomposition.py:53: in single_split
return N*D.inv()
_multivector.py:795: in inv
return self._pick_inv(fallback=True)
_multivector.py:767: in _pick_inv
return self.hitzer_inverse()
_multivector.py:741: in hitzer_inverse
return self.layout._hitzer_inverse(self)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> raise ValueError('Multivector has no inverse')
E ValueError: Multivector has no inverse
_layout.py:634: ValueError However, if I split |
… and odd seperatelly to accomodate for this.
I've now added tests for In the spaces 3-5-1, 4-4-1, 5-3-0, and 5-3-1 I now get an error that the log doesn't work up to the desired precision, but that appears to be purely a numerical precision problem, its still correct-ish since @hugohadfield, is it possible to only iterate spaces with |
I think adding the dimension dependent accuracy check is a good idea, it would also be a good idea to profile the tests a little and see where the code is spending most of its time, unfortunately I suspect it is in algebra creation. We should also think about maybe numba JITing the various functions in this PR, that way users can include them inside jitted functions for speed etc. |
Codecov Report
@@ Coverage Diff @@
## master #398 +/- ##
===========================================
- Coverage 71.37% 32.59% -38.78%
===========================================
Files 73 75 +2
Lines 9459 9635 +176
Branches 1211 1244 +33
===========================================
- Hits 6751 3141 -3610
- Misses 2538 6381 +3843
+ Partials 170 113 -57
Continue to review full report at Codecov.
|
@_numba_utils.njit(cache=True) | ||
def _single_split_even_values(Wm_array, li, r): | ||
ND = np.zeros((2, Wm_array[0, :].shape[0]), dtype=np.complex_) | ||
for i in range(0, Wm_array.shape[0]//2+1): | ||
ND[0, :] += Wm_array[2*i, :] * li**(r - i) | ||
for i in range(0, Wm_array.shape[0]//2): | ||
ND[1, :] += Wm_array[2*i+1, :] * li**(r - i - 1) | ||
return ND | ||
|
||
|
||
@_numba_utils.njit(cache=True) | ||
def _single_split_odd_values(Wm_array, li, r): | ||
ND = np.zeros((2, Wm_array[0, :].shape[0]), dtype=np.complex_) | ||
for i in range(0, Wm_array.shape[0]//2): | ||
ND[0, :] += Wm_array[2 * i + 1, :] * li ** (r - i) | ||
ND[1, :] += Wm_array[2*i, :] * li**(r - i) | ||
return ND | ||
|
||
|
||
def single_split_even(Wm, li, r): | ||
"""Helper function to compute a single split for a given set of W_m and | ||
eigenvalue lambda_i, when the total number of terms in the split is even. | ||
""" | ||
Wm_array = np.array([W.value for W in Wm]) | ||
ND = _single_split_even_values(Wm_array, li, r) | ||
N = Wm[0].layout.MultiVector(ND[0, :]) | ||
D = Wm[0].layout.MultiVector(ND[1, :]) | ||
return N*D.leftLaInv() | ||
|
||
|
||
def single_split_odd(Wm, li, r): | ||
"""Helper function to compute a single split for a given set of W_m and | ||
eigenvalue lambda_i, when the total number of terms in the split is odd. | ||
""" | ||
Wm_array = np.array([W.value for W in Wm]) | ||
ND = _single_split_odd_values(Wm_array, li, r) | ||
N = Wm[0].layout.MultiVector(ND[0, :]) | ||
D = Wm[0].layout.MultiVector(ND[1, :]) | ||
return N*D.leftLaInv() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hugohadfield, I assume this jitted code is your doing. Is there a reason you define the values
function vs just using the multivector jit support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe #412 will resolve this.
whats the status on this? any chance we could get it pushed in some form so i can use it? |
just checking in again. this is a really great feature! |
This PR aims to add the bivector split of chapter 6 of my thesis to
clifford
. When finished, this PR will add the decomposition of arbitrary bivectors into mutually committing orthogonal simple bivectors, and the decomposition of rotors into mutually commuting orthogonal simple rotors. Additionally, this enables us to implement closed form implementations for the exp/log function of simple bivectors/rotors respectively.ToDo
Curious to hear your opinions on the work done so far.