From bd449baef7050adf1b12acedcbc72a4f7bae2400 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Tue, 22 Oct 2019 14:05:37 +0100 Subject: [PATCH] Change `isBlade` to work on denegerate metrics ```python >>> import clifford >>> layout, _ = clifford.Cl(3, 0, 1, firstIdx=0) >>> locals().update(layout.blades) >>> e0.isBlade() True >>> e0.isBlade(invertible=True) False ``` --- clifford/_multivector.py | 27 ++++++++++++++++++++------- clifford/test/test_clifford.py | 21 ++++++++++++++++++++- clifford/test/test_degenerate.py | 9 +++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/clifford/_multivector.py b/clifford/_multivector.py index 3d760c51..b431a011 100644 --- a/clifford/_multivector.py +++ b/clifford/_multivector.py @@ -2,6 +2,7 @@ import math from typing import List, Set, Tuple, Union import warnings +import operator import numpy as np @@ -645,25 +646,37 @@ 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 # 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 @@ -671,7 +684,7 @@ def isVersor(self) -> bool: # 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 diff --git a/clifford/test/test_clifford.py b/clifford/test/test_clifford.py index 3ff241f6..89534561 100644 --- a/clifford/test/test_clifford.py +++ b/clifford/test/test_clifford.py @@ -631,7 +631,6 @@ def test_categorization(self, g3): assert v.isVersor() neither = [ - layout.scalar*0, 1 + e1, 1 + (e1^e2^e3) ] @@ -639,6 +638,26 @@ def test_categorization(self, g3): 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'] diff --git a/clifford/test/test_degenerate.py b/clifford/test/test_degenerate.py index 16968cc2..f1f2aa0c 100644 --- a/clifford/test/test_degenerate.py +++ b/clifford/test/test_degenerate.py @@ -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