-
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
Open
tBuLi
wants to merge
47
commits into
pygae:master
Choose a base branch
from
tBuLi:decomposition
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
64a7131
Added bivector split for arbitrary GAs and tests thereof.
0b0c76a
Added rotor_split.
6a5dd22
Parameterized the tests, added testng for rotor_split.
f4e56da
Stop checking if a MV matrix is invertible and let numpy decide.
6d64658
Normalize complex simple rotors correctly
e823281
Added closed form rotor logarithm.
4d6fcbc
Added tests for closed form rotor logarithm.
1149fb2
Fixed flake8 errors.
8cdf6be
Improved comments in the tests.
5215235
Add a basic test to verify assumptions about the bivectors produced b…
hugohadfield 5ebe817
Merge branch 'decomposition' of https://github.com/tBuLi/clifford int…
58e078c
Added check for simplicity to the unknown_split test
5404367
Added bivector split for arbitrary GAs and tests thereof.
68908ca
Added rotor_split.
3254634
Parameterized the tests, added testng for rotor_split.
3afcdd3
Stop checking if a MV matrix is invertible and let numpy decide.
b81e15d
Normalize complex simple rotors correctly
559773c
Added closed form rotor logarithm.
4372805
Added tests for closed form rotor logarithm.
5200950
Fixed flake8 errors.
d744819
Improved comments in the tests.
47c8e41
Add a basic test to verify assumptions about the bivectors produced b…
hugohadfield 7e72138
Added check for simplicity to the unknown_split test
aa5d283
change assertions slighlty
hugohadfield 341dd07
Prefer np.testing.allclose vs np.testing.assert_almost_equal
hugohadfield d9115d4
Failing tests were due to Bs and ls not always being returned in the …
647efdf
Removed print statement
d28fa34
Improve type stability of exp and log, reduce required tolerance a bi…
hugohadfield 68a18bb
Tag the bivector split as too slow when v high dimensional
hugohadfield 5d82fa3
Added Roelfs thesis to bibliography
1322ebf
Merge branch 'decomposition' of https://github.com/tBuLi/clifford int…
89ceada
Merge remote-tracking branch 'origin/master' into decomposition
hugohadfield 087aea0
Sort eigenvalues from boost to rotation, implement the split for even…
9234594
Boosts should never hve a negative scalar part.
47166a6
Update known_splits to new sort order
18ee52a
Test that exp(log(R)) == R
139250c
Make the accuracy dependent on total dimension
hugohadfield 21be96d
JIT the internal helper function
hugohadfield 424ed7b
Fix linting
hugohadfield e8bb4ff
Tidy up the tests to be more granular and not impact the startup time…
eric-wieser 90d232e
Merge remote-tracking branch 'upstream/master' into decomposition
eric-wieser ca30432
Ensure docs are generated
eric-wieser 5c8e386
flake8
eric-wieser 7d5d0ac
Fix docstrings, flake8
eric-wieser 9bfe9cc
try to fix CI
eric-wieser 025d624
Fix doctest
eric-wieser e206c24
Merge remote-tracking branch 'upstream/master' into decomposition
eric-wieser File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
""" | ||
.. currentmodule:: clifford.invariant_decomposition | ||
|
||
===================================================== | ||
invariant_decomposition (:mod:`clifford.invariant_decomposition`) | ||
===================================================== | ||
|
||
.. versionadded:: 1.5.0 | ||
|
||
|
||
This file implements the invariant decomposition (aka bivector split) of bivectors into | ||
mutually commuting orthogonal simple bivectors, based on the method of :cite:`roelfs2021thesis`, chapter 6. | ||
|
||
The invariant decomposition enables closed form exponentials and logarithms, and the factorization of | ||
rotors into simple rotors. | ||
|
||
Example usage:: | ||
|
||
>>> from clifford.g4 import * | ||
>>> B = 1*e12 + 2*e34 | ||
>>> bivector_split(B) | ||
[((1+0j)^e12), ((2+0j)^e34)] | ||
|
||
Implemented functions | ||
--------------------- | ||
|
||
.. autofunction:: bivector_split | ||
.. autofunction:: rotor_split | ||
.. autofunction:: exp | ||
.. autofunction:: log | ||
|
||
|
||
Helper functions | ||
---------------- | ||
|
||
.. autofunction:: _bivector_split | ||
.. autofunction:: single_split | ||
|
||
""" | ||
import math | ||
from functools import reduce | ||
|
||
import numpy as np | ||
|
||
from ._settings import _eps | ||
from . import _numba_utils | ||
|
||
|
||
@_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() | ||
|
||
|
||
def _bivector_split(Wm, return_all=True): | ||
"""Internal helper function to perform the decomposition, given a set of Wm. | ||
|
||
Parameters | ||
---------- | ||
return_all : bool, optional | ||
If `True`, returns all the :math:`b_i`. | ||
If `False`, return all :math:`b_i` except for the one with the smallest magnitude. | ||
""" | ||
# The effective value of k is determined by the largest non-zero W. | ||
# remove the highest grade zeros to prevent meaningless lambda_i = 0 values. | ||
for W in Wm[::-1]: | ||
if np.linalg.norm(W.value) > _eps: | ||
break | ||
else: | ||
Wm = Wm[:-1] | ||
|
||
k = (len(Wm) - 1) | ||
r = k // 2 | ||
Wm_sq = np.array([(W ** 2).value[0] * (-1) ** (k - m) for m, W in enumerate(Wm)]) | ||
ls = np.roots(Wm_sq) | ||
|
||
Bs = [] | ||
# Sort to have the value closest to zero last. | ||
ls_sorted = sorted(ls, key=lambda li: -li) | ||
# Exclude the smallest value if asked. | ||
for li in (ls_sorted if return_all else ls_sorted[:-1]): | ||
if k % 2 == 0: | ||
Bs.append(single_split_even(Wm, li, r)) | ||
else: | ||
Bs.append(single_split_odd(Wm, li, r)) | ||
return (Bs, ls_sorted) | ||
|
||
|
||
def bivector_split(B, k=None, roots=False): | ||
r"""Bivector split of the bivector B based on the method of :cite:`roelfs2021thesis`, chapter 6. | ||
|
||
Parameters | ||
---------- | ||
roots : bool, optional | ||
If `True`, return the values of the :math:`\lambda_i` in addition to the :math:`b_i`. | ||
If `False`, return only the :math:`b_i`. | ||
""" | ||
dim = B.layout.dims | ||
if k is None: | ||
k = dim // 2 | ||
|
||
Wm = [(B**m)(2*m) / math.factorial(m) for m in range(0, k + 1)] | ||
Bs, ls = _bivector_split(Wm, return_all=False) | ||
Bs = Bs + [B - sum(Bs)] | ||
return (Bs, ls) if roots else Bs | ||
|
||
|
||
def rotor_split(R, k=None, roots=False): | ||
dim = R.layout.dims | ||
if k is None: | ||
k = dim // 2 | ||
|
||
Wm = [R(2 * m) for m in range(0, k + 1)] | ||
Ts, ls = _bivector_split(Wm, return_all=False) | ||
|
||
Rs = [(1 + ti) for ti in Ts] | ||
Rs = [Ri.normal() if np.isreal((Ri*~Ri).value[0]) else Ri / np.sqrt((Ri*~Ri).value[0]) for Ri in Rs] | ||
P = reduce(lambda tot, x: tot*x, Rs, 1.0 + 0.0*R) | ||
Rs = Rs + [R*P.leftLaInv()] | ||
return (Rs, ls) if roots else Rs | ||
|
||
|
||
def exp(B): | ||
Bs, ls = bivector_split(B, roots=True) | ||
R = B.layout.scalar | ||
for Bi, li in zip(Bs, ls): | ||
if np.isreal(li) and li < 0: | ||
beta_i = np.sqrt(-li) | ||
R *= np.cos(beta_i) + (np.sin(beta_i) / beta_i) * Bi | ||
elif np.isreal(li) and np.abs(li) < _eps: | ||
R *= 1 + Bi | ||
else: | ||
beta_i = np.sqrt(li) | ||
R *= np.cosh(beta_i) + (np.sinh(beta_i) / beta_i) * Bi | ||
return R | ||
|
||
|
||
def log(R): | ||
Rs, ls = rotor_split(R, roots=True) | ||
logR = R.layout.MultiVector() | ||
for Ri, li in zip(Rs, ls): | ||
if li < 0: | ||
norm = np.sqrt(- (Ri(2) ** 2).value[0]) | ||
logR += np.arccos(Ri.value[0]) * Ri(2) / norm | ||
elif np.abs(li) < _eps: | ||
logR += Ri(2) | ||
else: | ||
norm = np.sqrt((Ri(2)**2).value[0]) | ||
logR += np.arccosh(Ri.value[0]) * Ri(2) / norm | ||
return logR | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.