test_cheatsheet.py
1 """ 2 Regression tests validating galgebra against Russell Goyder's geometric 3 algebra cheat sheet (https://russellgoyder.github.io/geometric-algebra-cheat-sheet/). 4 5 Covers all 7 sections in G(3,0): geometric product, vector-multivector 6 products, bivector identities, commutator product, pseudoscalar, 7 outermorphism/rotation, and adjoint. 8 """ 9 import pytest 10 from sympy import symbols, cos, sin, S, simplify, trigsimp 11 12 from galgebra.ga import Ga 13 14 15 @pytest.fixture(scope='module') 16 def g3(): 17 ga, e1, e2, e3 = Ga.build('e*1|2|3', g=[1, 1, 1]) 18 return ga, e1, e2, e3 19 20 21 @pytest.fixture(scope='module') 22 def vecs(g3): 23 ga, e1, e2, e3 = g3 24 a1, a2, a3 = symbols('a1 a2 a3', real=True) 25 b1, b2, b3 = symbols('b1 b2 b3', real=True) 26 c1, c2, c3 = symbols('c1 c2 c3', real=True) 27 d1, d2, d3 = symbols('d1 d2 d3', real=True) 28 a = a1*e1 + a2*e2 + a3*e3 29 b = b1*e1 + b2*e2 + b3*e3 30 c = c1*e1 + c2*e2 + c3*e3 31 d = d1*e1 + d2*e2 + d3*e3 32 return a, b, c, d 33 34 35 def test_geometric_product_decomposition(g3, vecs): 36 """Section 1: ab = a·b + a∧b, inner/outer as symmetric/antisymmetric parts.""" 37 ga, e1, e2, e3 = g3 38 a, b, c, d = vecs 39 40 assert simplify((a*b - (a|b) - (a^b)).obj) == 0 41 assert simplify(((a|b) - ga.mv(S.Half)*(a*b + b*a)).obj) == 0 42 assert simplify(((a^b) - ga.mv(S.Half)*(a*b - b*a)).obj) == 0 43 44 45 def test_vector_multivector_products(g3, vecs): 46 """Section 2: a*B = a<B + a∧B (grade raising/lowering).""" 47 ga, e1, e2, e3 = g3 48 a, b, c, d = vecs 49 50 B = b ^ c 51 assert simplify((a*B - (a < B) - (a ^ B)).obj) == 0 52 53 T = b ^ c ^ d 54 assert simplify((a*T - (a < T) - (a ^ T)).obj) == 0 55 56 57 def test_bivector_identities(g3, vecs): 58 """Section 3: a·(b∧c), (a∧b)·B associativity, (a∧b)·(c∧d) scalar formula.""" 59 ga, e1, e2, e3 = g3 60 a, b, c, d = vecs 61 62 # a·(b∧c) = (a·b)c - (a·c)b 63 lhs1 = a < (b^c) 64 rhs1 = (a|b)*c - (a|c)*b 65 assert simplify((lhs1 - rhs1).obj) == 0 66 67 # (a∧b)·B = a·(b·B) (left contraction associates) 68 lhs2 = (a^b) < (c^d) 69 rhs2 = a < (b < (c^d)) 70 assert simplify((lhs2 - rhs2).obj) == 0 71 72 # (a∧b)·(c∧d) = (b·c)(a·d) - (a·c)(b·d) 73 lhs3 = (a^b) | (c^d) 74 rhs3 = (b|c)*(a|d) - (a|c)*(b|d) 75 assert simplify((lhs3 - rhs3).obj) == 0 76 77 78 def test_commutator_product(g3, vecs): 79 """Section 4: B×v = B·v, grade-preserving on bivectors, Jacobi identity.""" 80 ga, e1, e2, e3 = g3 81 a, b, c, d = vecs 82 83 def comm(X, Y): 84 return ga.mv(S.Half) * (X*Y - Y*X) 85 86 B2 = e1 ^ e2 87 for v in [e1, e2, e3, a]: 88 assert simplify((comm(B2, v) - (B2 | v)).obj) == 0 89 90 B3 = e2 ^ e3 91 assert (comm(B2, B3)).pure_grade() == 2 92 93 B4 = e1 ^ e3 94 jacobi = comm(B2, comm(B3, B4)) + comm(B3, comm(B4, B2)) + comm(B4, comm(B2, B3)) 95 assert simplify(jacobi.obj) == 0 96 97 98 def test_pseudoscalar(g3): 99 """Section 5: I²=-1, I central in G(3,0), dual via I⁻¹.""" 100 ga, e1, e2, e3 = g3 101 I = ga.I() 102 103 assert (I*I).scalar() == S.NegativeOne 104 105 # (-1)^(r*(n-1)) = (-1)^(2r) = 1 for all r in 3D: I is central 106 for mv in [e1, e1^e2, I]: 107 assert simplify((I*mv - mv*I).obj) == 0 108 109 # dual(A) = A * I^{-1} (Iinv+ convention, not galgebra's built-in dual() 110 # which uses I+ i.e. dual(A) = I*A — a different sign convention) 111 assert simplify((I.inv() + I).obj) == 0 # I^{-1} = -I when I^2=-1 112 assert e1 * I.inv() == -(e2 ^ e3) 113 114 115 def test_outermorphism_rotation(g3): 116 """Section 6: rotor normalization, rotation sandwich, det(rotation)=1.""" 117 ga, e1, e2, e3 = g3 118 theta = symbols('theta', real=True) 119 R = ga.mv(cos(theta/2)) - ga.mv(sin(theta/2)) * (e1^e2) 120 121 assert trigsimp((R * ~R).scalar()) == 1 122 123 rotated = ga.mv(trigsimp((R * e1 * ~R).obj)) 124 assert trigsimp((rotated - ga.mv(cos(theta))*e1 - ga.mv(sin(theta))*e2).obj) == 0 125 126 L = ga.lt(lambda x: R*x*~R) 127 assert trigsimp(L.det()) == 1 128 129 130 def test_adjoint(g3, vecs): 131 """Section 7: a·F̄(b) = F(a)·b and det(FG) = det(F)·det(G).""" 132 ga, e1, e2, e3 = g3 133 a, b, c, d = vecs 134 135 L = ga.lt([e1, e1 + e2, e3]) 136 assert simplify((a | L(b) - L.adj()(a) | b).obj) == 0 137 138 theta, phi = symbols('theta phi', real=True) 139 R1 = ga.mv(cos(theta/2)) - ga.mv(sin(theta/2)) * (e1^e2) 140 R2 = ga.mv(cos(phi/2)) - ga.mv(sin(phi/2)) * (e2^e3) 141 L1 = ga.lt(lambda x: R1*x*~R1) 142 L2 = ga.lt(lambda x: R2*x*~R2) 143 L12 = ga.lt(lambda x: L1(L2(x))) 144 assert trigsimp(L12.det() - L1.det()*L2.det()) == 0