From 1bbdb59a753883cc1c626b2898f6675b6191b8e0 Mon Sep 17 00:00:00 2001 From: Eelco Hoogendoorn <> Date: Sat, 4 Nov 2023 10:27:50 +0100 Subject: [PATCH] operator checks --- numga/algebra/test/test_algebra.py | 28 +++++++++++++------------- numga/backend/test/test_numpy.py | 32 +++++++++++++++++++++--------- numga/multivector/extension.py | 4 ---- numga/operator/operator.py | 14 +++++-------- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/numga/algebra/test/test_algebra.py b/numga/algebra/test/test_algebra.py index a90f0ba..f98f9fb 100644 --- a/numga/algebra/test/test_algebra.py +++ b/numga/algebra/test/test_algebra.py @@ -24,7 +24,7 @@ def test_complex(): full = complex.subspace.full() even = complex.subspace.even_grade() - assert even.is_n_simple(1) + assert even.simplicity == 1 c, s = complex.product(even.blades, even.blades) print(c) print(s) @@ -63,17 +63,17 @@ def test_2d_pga(): rotation_vector = bivector.nondegenerate() translation_vector = bivector.degenerate() - assert scalar.is_n_simple(0) - assert vector.is_n_simple(1) - assert bivector.is_n_simple(1) - assert rotation_vector.is_n_simple(1) + assert scalar.simplicity == 0 + assert vector.simplicity == 1 + assert bivector.simplicity == 1 + assert rotation_vector.simplicity == 1 assert not rotation_vector.is_degenerate assert len(rotation_vector) == 1 - assert translation_vector.is_n_simple(1) + assert translation_vector.simplicity == 1 assert translation_vector.is_degenerate assert len(translation_vector) == 2 - assert even_grade.is_reverse_n_simple(1) + assert even_grade.simplicity == 1 assert even_grade.is_subalgebra print(full.bit_blades()) @@ -104,17 +104,17 @@ def test_3d_pga(): # FIXME: translation motor would be translation_vector + scalar # should we call it rotor/translator? and is there a natural way to construct it? - assert vector.is_n_simple(1) - assert bivector.is_n_simple(2) - assert rotation_vector.is_n_simple(1) + assert vector.simplicity == 1 + assert bivector.simplicity == 2 + assert rotation_vector.simplicity == 1 assert not rotation_vector.is_degenerate assert len(rotation_vector) == 3 - assert translation_vector.is_n_simple(1) + assert translation_vector.simplicity == 1 assert translation_vector.is_degenerate assert len(translation_vector) == 3 - assert trivector.is_n_simple(1) - assert even_grade.is_n_simple(2) - assert quaternion.is_n_simple(1) + assert trivector.simplicity == 1 + assert even_grade.simplicity == 2 + assert quaternion.simplicity == 1 print(full.bit_blades()) diff --git a/numga/backend/test/test_numpy.py b/numga/backend/test/test_numpy.py index e417dae..a964bd4 100644 --- a/numga/backend/test/test_numpy.py +++ b/numga/backend/test/test_numpy.py @@ -144,10 +144,14 @@ def test_inverse_exhaustive(descr): for grades in itertools.combinations(all_grades, r+1): try: V = ga.subspace.from_grades(list(grades)) - print() - print(V.simplicity, list(grades), end='') + # print() x = random_subspace(ga, V, (N,)) - check_inverse(x, x.inverse(), atol=1e-10) + i = x.inverse() + check_inverse(x, i, atol=1e-10) + + print(V.simplicity, list(grades), list(np.unique(i.subspace.grades())), end='') + print() + print(i) except: pass @@ -234,18 +238,28 @@ def test_inverse_degenerate(): def test_inverse_simplicifation_failure(): """succssive involute products sometimes fail to simplify fully. - this results in extra recursion and poorer high dim genealization + this results in extra recursion and poorer high dim generalization, + and also sometimes dummy zero output grades """ ga = NumpyContext(Algebra.from_pqr(5,0,0)) V = ga.subspace.from_grades([1,2,5]) + assert V.simplicity == 3 # in reality its two but we lack the symbolic logic to see it x = random_subspace(ga, V, (1,)) - x.inverse() - print() + # can still invert correctly in 3 steps tho + check_inverse(x, x.inverse()) + y = x.symmetric_reverse_product() z = y.symmetric_pseudoscalar_negation_product() - print(x) - print(y) - print(z) + assert z.subspace == ga.subspace.from_grades([0, 5]) + assert np.allclose(z.select[5].values, 0) + + V = ga.subspace.from_grades([2]) + assert V.simplicity == 2 # need two steps; but can do without the extra zeros + x = random_subspace(ga, V, (1,)) + i = x.inverse() + assert i.subspace == ga.subspace.from_grades([2, 4]) + check_inverse(x, i) + assert np.allclose(i.select[4].values, 0) def test_inverse_6d(): diff --git a/numga/multivector/extension.py b/numga/multivector/extension.py index 669e022..82251c0 100644 --- a/numga/multivector/extension.py +++ b/numga/multivector/extension.py @@ -31,10 +31,6 @@ # NOTE: these methods have a study_ prefix; # since otherwise theyd shadow equally named functionality with subtly different outcomes -# mv.study_conjugate = SubspaceDispatch("""Study conjugation; tack a minus sign on grade 4 components""") -# @mv.study_conjugate.register(lambda s: s.inside.study()) -# def study_conjugate(s: Study) -> Study: -# return s.operator.study_conjugate(s.subspace)(s) mv.study_norm_squared = SubspaceDispatch("""Study norm squared""") @mv.study_norm_squared.register(lambda s: s.inside.study()) def study_norm_squared(s: Study) -> Scalar: diff --git a/numga/operator/operator.py b/numga/operator/operator.py index fafcc59..f810e87 100644 --- a/numga/operator/operator.py +++ b/numga/operator/operator.py @@ -117,15 +117,11 @@ def symmetry(self, axes: Tuple[int, int], sign: int) -> "Operator": # FIXME: is it appropriate to view everything as real numbers at this point? # i think so; but i keep confusing myself assert self.axes[axes[0]] == self.axes[axes[1]] - return Operator( - # FIXME: integer division not always appropriate. - # if not, user responsible for slapping on a multiplication factor? - # or do runtime check for inappropriate divisions / remainders? - - # FIXME: yeah such a check would be good... already burned myself here with ineria map! - (self.kernel + sign * np.swapaxes(self.kernel, *axes)) // 2, - self.axes - ).squeeze() + kernel2 = self.kernel + sign * np.swapaxes(self.kernel, *axes) + kernel = kernel2 // 2 + if not np.all(kernel * 2 == kernel2): + raise Exception('inappropriate integer division') + return Operator(kernel, self.axes).squeeze() def fuse(self, other: "Operator", axis: int) -> "Operator": """Fuse to operators; feed output of `self` into nth input argument of `other`"""