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

Change isBlade to work on degenerate metrics #162

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions clifford/_multivector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import math
from typing import List, Set, Tuple, Union
import warnings
import operator

import numpy as np

Expand Down Expand Up @@ -645,33 +646,45 @@ def isScalar(self) -> bool:

return True

def isBlade(self) -> bool:
def isBlade(self, *, invertible=False) -> bool:
"""Returns true if multivector is a blade.
"""
if len(self.grades()) != 1:
if len(self.grades()) > 1:
return False

return self.isVersor()
return self.isVersor(invertible=invertible)

def isVersor(self) -> bool:
def isVersor(self, *, invertible=True) -> bool:
"""Returns true if multivector is a versor.
From :cite:`ga4cs` section 21.5, definition from 7.6.4
"""
if invertible:
grade_cmp = operator.eq
else:
grade_cmp = operator.le # subset

Vhat = self.gradeInvol()
Vrev = ~self
Vinv = Vrev/(self*Vrev)[()]
mag = (self*Vrev)[()]
if mag != 0:
# not strictly necessary, just scales to make eps appropriate below
Vinv = Vrev / mag
elif invertible:
return False
else:
Vinv = Vrev
Comment on lines +668 to +675
Copy link
Member Author

Choose a reason for hiding this comment

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

Changes here are to:

  • bail out early without a divide-by-zero if not allowing non-invertible blades
  • avoiding the division if it is zero, since that shouldn't affect the results below significantly
  • Use [()] instead of [0], which is a little more reliable for pulling out the scalar part


# Test if the versor inverse (~V)/(V * ~V) is truly the inverse of the
# multivector V
if (Vhat*Vinv).grades(eps=0.000001) != {0}:
if not grade_cmp((Vhat*Vinv).grades(eps=0.000001), {0}):
return False
if not np.sum(np.abs((Vhat*Vinv).value - (Vinv*Vhat).value)) < 0.0001:
return False

# applying a versor (and hence an invertible blade) to a vector should
# not change the grade
if not all(
(Vhat*e*Vrev).grades(eps=0.000001) == {1}
grade_cmp((Vhat*e*Vrev).grades(eps=0.000001), {1})
for e in cf.basis_vectors(self.layout).values()
):
return False
Expand Down
21 changes: 20 additions & 1 deletion clifford/test/test_clifford.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,14 +631,33 @@ def test_categorization(self, g3):
assert v.isVersor()

neither = [
layout.scalar*0,
1 + e1,
1 + (e1^e2^e3)
]
for n in neither:
assert not n.isBlade()
assert not n.isVersor()

def test_categorization_null(self, g3c):
layout = g3c
e1 = layout.blades['e1']

blades = [
layout.scalar*0,
layout.einf,
layout.eo,
e1^layout.eo
]
for b in blades:
# none of these are invertible
assert not b.isBlade(invertible=True)
assert not b.isVersor(invertible=True)

# but if we allow them to be non-invertible, they are blades and
# versors
assert b.isBlade(invertible=False)
assert b.isVersor(invertible=False)

def test_blades_of_grade(self, g3):
layout = g3
e1 = layout.blades['e1']
Expand Down
9 changes: 9 additions & 0 deletions clifford/test/test_degenerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ def POINT(x, y, z):
L = P1 & P2
assert L == -(4^e01) + (3^e02) - (1^e12) - (4^e13) + (3^e23)

def test_is_blade(self):
blades = self.layout.blades
e0 = blades['e0']
e1 = blades['e1']

for b in [e0, e0^e1]:
assert e0.isBlade()
assert not e0.isBlade(invertible=True)

def test_no_crash(self):
""" TODO: This doesn't actually do any asserts! """
layout = self.layout
Expand Down