From d663c331c9b73af2887f0f5289d3eed251fb666a Mon Sep 17 00:00:00 2001 From: utensil Date: Tue, 25 Mar 2025 11:51:05 +0800 Subject: [PATCH 1/5] Add a justfile to run tests easier locally --- justfile | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 justfile diff --git a/justfile b/justfile new file mode 100644 index 00000000..bf81c64e --- /dev/null +++ b/justfile @@ -0,0 +1,33 @@ +# This is a Justfile. It is a file that contains commands that can be run with the `just` command. +# To install `just`, see https://github.com/casey/just + +default: + just --list + +prep-uv: + #!/usr/bin/env bash + # Already installed, then exit + which uv && exit 0 + # On Mac, install with Homebrew + which brew && brew install uv + # On *nix + which uv || (curl -LsSf https://astral.sh/uv/install.sh | sh) + # On Windows, see https://docs.astral.sh/uv/getting-started/installation/ + +prep-dt: + #!/usr/bin/env bash + which uv || (echo "Please install 'uv' in your preferred way, e.g. just prep-uv" && exit 1) + uv venv --python=python3.11 --seed + source .venv/bin/activate + pip install -e . + pip install -r test_requirements.txt + +# Run tests during development +# depends on prep-dt +dt TESTS="*": + #!/usr/bin/env bash + source .venv/bin/activate + pytest test/test_{{TESTS}}.py + +lint: + flake8 -v From 9c7af64a55411f5d8bc3dbfb32d65a024d604322 Mon Sep 17 00:00:00 2001 From: utensil Date: Tue, 25 Mar 2025 11:51:33 +0800 Subject: [PATCH 2/5] Add tests for is_versor --- test/test_versors.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 test/test_versors.py diff --git a/test/test_versors.py b/test/test_versors.py new file mode 100644 index 00000000..e1fe4517 --- /dev/null +++ b/test/test_versors.py @@ -0,0 +1,49 @@ +import unittest +from galgebra.ga import Ga +from sympy import S + + +class TestVersors(unittest.TestCase): + def setUp(self): + # Set up different geometric algebras for testing + + # G(3,0) - Euclidean 3-space + self.g3d = Ga("e1 e2 e3", g=[1, 1, 1]) + + # G(1,3) - Spacetime algebra + self.sta = Ga("e0 e1 e2 e3", g=[1, -1, -1, -1]) + + # Initialize basis vectors + e1, e2, e3 = self.g3d.mv() + e0, e1, e2, e3 = self.sta.mv() + + def test_basis_vectors_are_versors(self): + """Individual basis vectors should be versors""" + e1, e2, e3 = self.g3d.mv() + self.assertTrue(e1.is_versor()) + self.assertTrue(e2.is_versor()) + self.assertTrue(e3.is_versor()) + + def test_mixed_grades_not_versor(self): + """A sum of different grades cannot be a versor""" + e1, e2, e3 = self.g3d.mv() + mixed = e1 + e2 * e3 # vector + bivector + self.assertFalse(mixed.is_versor()) + + def test_dorst_counterexample(self): + """Test Greg1950's counterexample from the spacetime algebra""" + e0, e1, e2, e3 = self.sta.mv() + V = S.One + self.sta.I() # 1 + I + self.assertFalse(V.is_versor()) + + def test_rotors_are_versors(self): + """Test that rotors (even-grade versors) are properly detected""" + e1, e2, e3 = self.g3d.mv() + rotor = S.One + e1 * e2 # 1 + e1e2 + self.assertTrue(rotor.is_versor()) + + def test_null_is_not_versor(self): + """Test that null multivectors are not versors""" + e1, e2, e3 = self.g3d.mv() + null_mv = e1 + e1 * self.g3d.I() # v + vI (squares to 0) + self.assertFalse(null_mv.is_versor()) From ef345afff1bccd285bc20524e2e12952fe2c45d6 Mon Sep 17 00:00:00 2001 From: utensil Date: Tue, 25 Mar 2025 12:17:11 +0800 Subject: [PATCH 3/5] Improve is_versor() Co-authored-by: Greg Grunberg --- galgebra/mv.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/galgebra/mv.py b/galgebra/mv.py index e8f7ea1f..296cd83d 100644 --- a/galgebra/mv.py +++ b/galgebra/mv.py @@ -935,25 +935,37 @@ def is_base(self) -> bool: def is_versor(self) -> bool: """ - Test for versor (geometric product of vectors) + Test if `self` is a versor. - This follows Leo Dorst's test for a versor. - Leo Dorst, 'Geometric Algebra for Computer Science,' p.533 - Sets self.versor_flg and returns value - """ + A versor is defined as a geometric product of finitely-many nonnull vectors. + + According to Greg Grunberg's analysis, the following are necessary and sufficient + conditions for a multivector V to be a versor: + + 1. V*V.rev() must be a nonzero scalar + 2. V.g_invol()*x*V.rev() must be a vector whenever x is a vector + + Sets self.versor_flg and returns value. + See https://github.com/pygae/galgebra/issues/533 for more discussions. + """ if self.versor_flg is not None: return self.versor_flg + self.characterise_Mv() - self.versor_flg = False self_rev = self.rev() - # see if self*self.rev() is a scalar - test = self*self_rev - if not test.is_scalar(): + + # Test condition 1: V*V.rev() must be a nonzero scalar + VVrev = self * self_rev + if not (VVrev.is_scalar() and not VVrev.is_zero()): + self.versor_flg = False return self.versor_flg - # see if self*x*self.rev() returns a vector for x an arbitrary vector - test = self * self.Ga._XOX * self.rev() - self.versor_flg = test.is_vector() + + # Test condition 2: V.g_invol()*x*V.rev() must be a vector + # where x is a generic vector + # and self.Ga._XOX is a generic vector in self's geometric algebra + VinvolXVrev = self.g_invol() * self.Ga._XOX * self_rev + self.versor_flg = VinvolXVrev.is_vector() return self.versor_flg r''' From f210f6587221708fe991e4ccb5397c3cec12dc4b Mon Sep 17 00:00:00 2001 From: utensil Date: Tue, 25 Mar 2025 12:18:17 +0800 Subject: [PATCH 4/5] Add more tests for is_versor() --- test/test_versors.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/test_versors.py b/test/test_versors.py index e1fe4517..43ba95d7 100644 --- a/test/test_versors.py +++ b/test/test_versors.py @@ -47,3 +47,22 @@ def test_null_is_not_versor(self): e1, e2, e3 = self.g3d.mv() null_mv = e1 + e1 * self.g3d.I() # v + vI (squares to 0) self.assertFalse(null_mv.is_versor()) + + def test_scalar_versor(self): + """Test that nonzero scalars are versors""" + self.assertTrue(self.g3d.mv(2).is_versor()) + self.assertFalse(self.g3d.mv(0).is_versor()) + + def test_bivector_exponential(self): + """Test that exponentials of bivectors are versors""" + e1, e2, e3 = self.g3d.mv() + B = e1 * e2 # bivector + rotor = (B/2).exp() # exp(B/2) is a rotor + self.assertTrue(rotor.is_versor()) + + def test_product_of_vectors(self): + """Test that products of vectors are versors""" + e1, e2, e3 = self.g3d.mv() + v1 = e1 + 2*e2 # vector + v2 = 3*e1 - e3 # vector + self.assertTrue((v1 * v2).is_versor()) From 92731abed8c812623c9134ff8f0ddd23064a80b4 Mon Sep 17 00:00:00 2001 From: utensil Date: Tue, 25 Mar 2025 13:02:16 +0800 Subject: [PATCH 5/5] Fix lint --- galgebra/mv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/galgebra/mv.py b/galgebra/mv.py index 296cd83d..cae12bde 100644 --- a/galgebra/mv.py +++ b/galgebra/mv.py @@ -939,7 +939,7 @@ def is_versor(self) -> bool: A versor is defined as a geometric product of finitely-many nonnull vectors. - According to Greg Grunberg's analysis, the following are necessary and sufficient + According to Greg Grunberg's analysis, the following are necessary and sufficient conditions for a multivector V to be a versor: 1. V*V.rev() must be a nonzero scalar @@ -954,14 +954,14 @@ def is_versor(self) -> bool: self.characterise_Mv() self_rev = self.rev() - + # Test condition 1: V*V.rev() must be a nonzero scalar VVrev = self * self_rev if not (VVrev.is_scalar() and not VVrev.is_zero()): self.versor_flg = False return self.versor_flg - # Test condition 2: V.g_invol()*x*V.rev() must be a vector + # Test condition 2: V.g_invol()*x*V.rev() must be a vector # where x is a generic vector # and self.Ga._XOX is a generic vector in self's geometric algebra VinvolXVrev = self.g_invol() * self.Ga._XOX * self_rev