# Testing the automatic canonicalisation routines. # Simple tests with symmetric/anti-symmetric tensors, all # expressions vanishing identically by symmetry arguments. def test01(): __cdbkernel__=create_scope() a_{#}::AntiSymmetric; s_{#}::Symmetric; tst1:= a_{m n} s_{m n}; canonicalise(tst1) assert(tst1==0) tst2:= a_{n m q} s_{m n}; canonicalise(tst2) assert(tst2==0) tst3:= a_{m m}; canonicalise(tst3) assert(tst3==0) print('Test 01 passed') test01() def test02(): __cdbkernel__=create_scope() a_{#}::AntiSymmetric; s_{#}::Symmetric; obj4:= s_{m n m n}; canonicalise(obj4) tst4:= s_{m m n n}-@(obj4); collect_terms(tst4) assert(tst4==0) print('Test 02 passed') test02() # Tests with more complicated tableau symmetries. def test03(): __cdbkernel__=create_scope() s_{#}::Symmetric; R_{m n p q}::RiemannTensor; tst5:= R_{m n p q}*s_{p q}*t_{m n}; canonicalise(tst5) assert(tst5==0) print('Test 03 passed') test03() def test04(): __cdbkernel__=create_scope() dW_{m n p q r s}::TableauSymmetry(shape={2,2}, indices={2,3,4,5}). rtopen:= dW_{a b r3 r4 r1 r2}*q; canonicalise(rtopen) rtopentest:= dW_{a b r1 r2 r3 r4}*q - @(rtopen); collect_terms(rtopentest) assert(rtopentest==0) print('Test 04 passed') test04() # Here, the fact that the 'second' field of the index contractions involving 'a' # are compared _before_ the one of the 'b' leads to a sorting in which the first # form is preferred: def test05(): __cdbkernel__=create_scope() a2_{#}::AntiSymmetric; R_{m n p q}::RiemannTensor; obj6a:= R_{m n p q} a_{m n} a2_{p q}; obj6b:= R_{m n p q} a_{p q} a2_{m n}; canonicalise(obj6a) canonicalise(obj6b) tst6:= @(obj6a)-@(obj6b); collect_terms(tst6) assert(tst6==0) print('Test 05 passed') test05() # The key test that inspired it all: def test06(): __cdbkernel__=create_scope() F_{#}::AntiSymmetric; G_{#}::AntiSymmetric; R_{m n p q}::RiemannTensor; tst7:= R_{d1 d2 d3 d4} R_{d4 d5 d6 d7} F_{d1 d2 d6 d7} G_{d3 d5 r5 s5}; canonicalise(tst7) assert(tst7==0) print('Test 06 passed') test06() # A more complicated preferred-form case. The algorithm should be stable, # that is, it should give the same result independent of which representative # is chosen as input. def test07(): __cdbkernel__=create_scope() H_{#}::Symmetric; F_{#}::AntiSymmetric; R_{m n p q}::RiemannTensor; obj8a:= R_{d1 d2 d3 d4} R_{d6 d7 d4 d5} F_{d1 d2 d6 d7} H_{d3 d5 r5 s5}; obj8b:= R_{d1 d2 d3 d4} R_{d4 d5 d6 d7} F_{d1 d2 d6 d7} H_{d3 d5 r5 s5}; obj8c:= - R_{d1 d2 d3 d4} R_{d5 d4 d6 d7} F_{d1 d2 d6 d7} H_{d3 d5 r5 s5}; obj8d:= - R_{d3 d4 d1 d2} R_{d5 d4 d6 d7} F_{d1 d2 d6 d7} H_{d3 d5 r5 s5}; canonicalise(obj8a) canonicalise(obj8b) canonicalise(obj8c) canonicalise(obj8d) tst8b:= @(obj8a)-@(obj8b); collect_terms(_) assert(tst8b==0) tst8c:= @(obj8a)-@(obj8c); collect_terms(_) assert(tst8c==0) tst8d:= @(obj8a)-@(obj8d); collect_terms(_) assert(tst8d==0) print('Test 07 passed') test07() # Interchange with more than two identical objects. Note that this # does not require sort_product. def test08(): __cdbkernel__=create_scope() obj9a:= A_{m n p} A_{m n p} A_{r s t} A_{r s t}; obj9b:= A_{m n p} A_{r s t} A_{r s t} A_{m n p}; tst9:= @(obj9a)-@(obj9b); canonicalise(_) collect_terms(_) assert(tst9==0) print('Test 08 passed') test08() # Another one along the lines of the above. def test10(): __cdbkernel__=create_scope() a_{#}::AntiSymmetric; s_{#}::Symmetric; tst1:= a_{m n} s_{m n}; canonicalise(tst1) assert(tst1==0) tst2:= a_{n m q} s_{m n}; canonicalise(tst2) assert(tst2==0) tst3:= a_{m m}; canonicalise(tst3) assert(tst3==0) print('Test 10 passed') test10() def test11(): __cdbkernel__=create_scope() a_{#}::AntiSymmetric; s_{#}::Symmetric; obj4:= s_{m n m n}; canonicalise(obj4) tst4:= s_{m m n n}-@(obj4); assert(tst4==0) print('Test 11 passed') test11() # Portugal's non-trivial fifth-order zero: def test12(): __cdbkernel__=create_scope() R_{m n p q}::RiemannTensor. tst11:= R_{a b c d} R_{e f k h} R_{i a j e} R_{b c d i} R_{f k h j}; canonicalise(_) assert(tst11==0) print('Test 12 passed') test12() # Further tests of the same. def test13(): __cdbkernel__=create_scope() zeta_{m n}::Symmetric; tst12:= k_a k_b zeta_{a b} - k_b k_a zeta_{a b}; canonicalise(_) collect_terms(_) assert(tst12==0) print('Test 13 passed') test13() # This one used to trigger a bug with triple indices. def test13b(): __cdbkernel__=create_scope() obj13:= A_{a} * A_{a} * A_{b}; canonicalise(_) tst13:= A_{b} A_{a} A_{a} - @(obj13); collect_terms(_) assert(tst13==0) print('Test 13b passed') test13b() # Another one which at one time produced triple indices. def test14(): __cdbkernel__=create_scope() obj14:= C1_{d5} *k3_{d6} * k3_{d6} * k3_{d5}; canonicalise(_) tst14:= C1_{d5} *k3_{d5} * k3_{d6} * k3_{d6} - @(obj14); collect_terms(_) assert(tst14==0) print('Test 14 passed') test14() # More complicated Young tableau symmetries. def test15(): __cdbkernel__=create_scope() A_{a b c d}::TableauSymmetry(shape={1,1}, indices={1,2}); obj15:= q*A_{d c b a}; canonicalise(_) tst15:= q A_{d b c a} + @(obj15); collect_terms(_) assert(tst15==0) print('Test 15 passed') test15() # DAntiSymmetric is of course just a special case of TableauSymmetry # but we still need tests. def test16(): __cdbkernel__=create_scope() dF5_{a b c d e f}::DAntiSymmetric. obj16:= dF5_{m n p q r s} AA_{r n q m s p}; canonicalise(_) tst16:= -dF5_{m n p q r s} * AA_{n p q m r s} - @(obj16); collect_terms(_) assert(tst16==0) print('Test 16 passed') test16() # def test17(): # dF5_{a b c d e f}::DAntiSymmetric. # obj17:= dF5_{b1 a5 a4 a3 a2 a1} * q; # @indexsort(_) # tst17:= dF5_{b1 a1 a2 a3 a4 a5} * q - @(obj17) # collect_terms(_) # assert(tst17) # # Multiple tableaux. # def test18(): __cdbkernel__=create_scope() tt_{a b c d e f}::TableauSymmetry(shape={1,1,1,1}, indices={2,3,4,5}, shape={2}, indices={0,1}). obj18:= tt_{a2 a1 a7 a6 a4 a5}*a; canonicalise(_) tst18:= tt_{a1 a2 a4 a5 a6 a7}*a + @(obj18); collect_terms(_) assert(tst18==0) print('Test 18 passed') test18() # An old bug with KroneckerDelta; this could only be fixed properly # when the 'canonicalise' routine was rewritten to only use # TableauBase input (rather than hardcoded DAntiSymmetric and so on). # def test19(): __cdbkernel__=create_scope() \delta_{a b}::KroneckerDelta. obj19:= a \delta_{c b}; canonicalise(_) tst19:= a \delta_{b c} - @(obj19); collect_terms(_) assert(tst19==0) print('Test 19 passed') test19() # Partial derivatives next. def test20(): __cdbkernel__=create_scope() \diff{#}::PartialDerivative. B_{m n}::Symmetric. obj20:= \diff{A_{m}}_{n} B_{n m} - \diff{A_{m}}_{n} B_{m n}; canonicalise(_) collect_terms(_) assert(obj20==0) print('Test 20 passed') test20() # \diff{#}::PartialDerivative. # {m,n,p,q,r,s,t}::Indices(vector). # obj21:= \diff{A_{m n p}}_{q r} C_{r q} - \diff{A_{m n p}}_{q r} C_{q r}; # @indexsort(_) # canonicalise(_) # @rename_dummies(_) # collect_terms(_) # assert(obj21) # This one used to crash: def test22(): __cdbkernel__=create_scope() \diff{#}::PartialDerivative. g3_{m n}::Metric. obj22:= \diff{\phi}_{p} * \diff{g3_{m m1}}_{n}; canonicalise(_) tst22:= \diff{\phi}_{p} * \diff{g3_{m m1}}_{n} - @(obj22); collect_terms(_) assert(tst22==0) print('Test 22 passed') test22() # Including symmetries of the argument tensor: def test23(): __cdbkernel__=create_scope() \diff{#}::PartialDerivative. A_{\mu\nu}::AntiSymmetric. obj23:= q*\diff{A_{\mu\nu}}_{\mu\nu}; canonicalise(_) assert(obj23==0) print('Test 23 passed') test23() def test24(): __cdbkernel__=create_scope() {\mu,\nu,\rho,\sigma}::Indices(position=independent). \diff{#}::PartialDerivative. A_{\mu\nu}::AntiSymmetric. obj24:= \diff{A_{\mu\nu}}_{\rho\sigma} B^{\nu\mu}; canonicalise(_) tst24:= - \diff{A_{\mu\nu}}_{\rho\sigma} B^{\mu\nu} - @(obj24); assert(tst24==0) print('Test 24 passed') test24() # Do not permute tensors with indices in different positions. # FIXME: this already gives zero at rename_dummies, is that correct? def test25(): __cdbkernel__=create_scope() \diff{#}::PartialDerivative. {m,n,p,q,r,s,t,u,v,w,m1,m2,m3,m4,m5,m6,m7}::Indices(vector,position=fixed). g^{m n}::Symmetric; obj25:= g^{p q} \diff{g_{m n}}_{p} \diff{g_{r s}}_{q} T^{r s m n}; canonicalise(_) tst25:= g^{p q} \diff{g_{m n}}_{p} \diff{g_{r s}}_{q} * T^{m n r s} - @(obj25); rename_dummies(_) assert(tst25==0) print('Test 25 passed') test25() # Test 27 & 28: spinors and anti-commuting objects # def test27(): __cdbkernel__=create_scope() {m,n,p,q,r,s,t,u}::Indices(vector). psi10001_{m}::Spinor(type=Majorana, dimension=10). psi10001_{m}::SelfAntiCommuting. \Gamma_{#}::GammaMatrix. H_{m n p}::AntiSymmetric. obj27:= psi10001_{n} \Gamma_{s t u} psi10001_{m} H_{p q r}; canonicalise(_) tst27:= psi10001_{m} \Gamma_{s t u} psi10001_{n} H_{p q r} -@(obj27); collect_terms(_) assert(tst27==0) print('Test 27 passed') test27() def test28(): __cdbkernel__=create_scope() {m,n,p,q,r,s,t,u}::Indices(vector). psi10001_{m}::Spinor(type=Majorana, dimension=10). psi10001_{m}::SelfAntiCommuting. \Gamma_{#}::GammaMatrix. obj28:= psi10001_{n} \Gamma_{s} psi10001_{m} H_{p q r}; canonicalise(_) tst28:= psi10001_{m} \Gamma_{s} psi10001_{n} H_{p q r} + @(obj28); collect_terms(_) assert(tst28==0) print('Test 28 passed') test28() def test29(): __cdbkernel__=create_scope() {m,n,p,q,r,s,t,u}::Indices(vector). \psi_{m}::Spinor. \psi_{m}::SelfAntiCommuting. \bar{#}::DiracBar. \Gamma_{#}::GammaMatrix. obj28b:= \bar{\psi_{r}} A B \Gamma_{n p} C \psi_{m}; canonicalise(_) tst28b:= \bar{\psi_{m}} A B \Gamma_{n p} C \psi_{r} + @(obj28b); collect_terms(_) assert(tst28b==0) print('Test 28b passed') test29() # Single-term expressions. def test29b(): __cdbkernel__=create_scope() {m,n,p,q,r,s,t,u}::Indices(vector). dR_{m n p q r s}::TableauSymmetry(shape={4,2}, indices={2,3,0,1,4,5}). dR_{m n p q r s}::Traceless. tst29:= dR_{m n p q r r}; canonicalise(_) assert(tst29==0) print('Test 29b passed') test29b() # Extreme symmetries. def test30(): __cdbkernel__=create_scope() {a,b,c,d,e,f,g,h,i,j,k,l,m}::Indices(vector). {a,b,c,d,e,f,g,h,i,j,k,l,m}::Integer(0..9). \eps_{a b c d e f g h i j}::AntiSymmetric. W_{a b c d}::WeylTensor. A_{k l}::AntiSymmetric. obj30:= W_{a b d c} W_{f e g h} W_{i j k l} \eps_{a b c d e f g h i j} A_{l k}; canonicalise(_) tst30:= W_{a b c d} * W_{e f g h} * W_{i j k l} * \eps_{a b c d e f g h i j} * A_{k l} + @(obj30); collect_terms(_) assert(tst30==0) print('Test 30 passed') test30() # Inherited properties def test31(): __cdbkernel__=create_scope() {m,n,p,q,r}::Indices(vector). {m,n,p,q,r}::Integer(0..10) \bar{#}::DiracBar. \psi_{m}::Spinor. \psi_{m}::SelfAntiCommuting. H_{m n p}::AntiSymmetric. \Gamma_{#}::GammaMatrix. obj31:= \bar{\psi_{m}} \Gamma_{q n p} \psi_{m} H_{n p q}; canonicalise(_) tst31:= \bar{\psi_{m}} \Gamma_{n p q} \psi_{m} H_{n p q} - @(obj31); collect_terms(_) assert(tst31==0) print('Test 31 passed') test31() # Indices in different sets should not be mixed up. def test32(): __cdbkernel__=create_scope() {a,b,c,d,e,f,g#}::Indices(SUFive); {i,j,k,l,m,n#}::Indices(flavor); HppT_{j k x}::TableauSymmetry(shape={2}, indices={0,1}); \tenSp_{j a b}::TableauSymmetry(shape={1,1},indices={1,2}); obj32:= HppT_{i j x} * \tenSp_{i a b} * \fiveSp_{j a} ; canonicalise(_) tst32:= HppT_{i j x} * \tenSp_{i b a} * \fiveSp_{j a} + @(obj32); collect_terms(_) assert(tst32==0) print('Test 32 passed') test32() # # Complicated traceless property. # @reset; # {i,j,m,n,k,p,q,l,r,r#}::Indices(vector) # C_{m n p q}::WeylTensor # \nabla{#}::Derivative # \nabla_{r}{ C_{m n p q} }::SatisfiesBianchi # \nabla_{r}{ C_{m n p q} }::Traceless # \delta_{i j}::KroneckerDelta # # #tst33:= Q*\nabla_{j}{\nabla_{q}{C_{m n p q}}}; # #canonicalise(_) # #assert(tst33) # Test 34: numerical indices def test34(): __cdbkernel__=create_scope() A_{m? n?}::AntiSymmetric. tst34a:= A_{4 4}; canonicalise(_) assert(tst34a==0) print('Test 34 passed') test34() def test34b(): __cdbkernel__=create_scope() R_{m? n? p? q?}::RiemannTensor. tst34b:= R_{4 4 2 3}; canonicalise(_) rl:={ R_{m? m? n? p?} -> 0, R_{n? p? m? m?} -> 0 }; substitute(tst34b, rl) assert(tst34b==0) print('Test 34b passed') test34b() # Diagonal objects def test35b(): __cdbkernel__=create_scope() {m,n}::Integer; \delta_{m n}::Diagonal. obj35:= \delta_{1 2} * \delta_{1 2} - \delta_{1 1} * \delta_{2 2}; canonicalise(_) tst35:= - \delta_{1 1}\delta_{2 2} - @(obj35); collect_terms(_) assert(tst35==0) print('Test 35 passed') test35b() # Canonicalisation with upper/lower indices. def test36(): __cdbkernel__=create_scope() \Gamma{#}::GammaMatrix. {a,b,c,d,e}::Indices(vector, position=fixed). obj36:= \Gamma_{a c}^{b d}; canonicalise(_) tst36:= \Gamma_{a}^{b}_{c}^{d} + @(obj36); collect_terms(_) assert(tst36==0) print('Test 36 passed') test36() def test37(): __cdbkernel__=create_scope() {a,b,c,d,e}::Indices(vector, position=fixed). obj37:= B^{b} B_{b} A_{a} A^{a}; canonicalise(_) tst37:= B^{a} B_{a} A^{b} A_{b} - @(obj37); collect_terms(_) assert(tst37==0) print('Test 37 passed') test37() # Mixed abstract & numerical indices. # # @reset. # {a,b,c,d,e}::Indices(group). # {i,j,k}::Indices(vector). # \delta{#}::KroneckerDelta. # \eta^{a}_{i j}::TableauSymmetry(shape={1,1}, indices={1,2}). # \epsilon^{a b c}::EpsilonTensor. # \partial{#}::Derivative. # obj38:= \epsilon^{a b c} \delta^{b 3} \epsilon^{c d e} \delta^{e 3} \eta^{d}_{i j} x^{i}; # canonicalise(_) # attern indices # # @reset. # J_{\mu\nu}::AntiSymmetric. # obj39:= \eta_{#1? #3?} J_{#2? #4?} - \eta_{#1? #4?} J_{#2? #3?} - \eta_{#2? #3?} J_{#1? #4?} + \eta_{#2? #4?} J_{#1? #3?}; # canonicalise(_) # # # Test 40: equality signs involved (used to crash) # @reset. # Y_{i j}::AntiSymmetric. # obj40:= X_{i} = Y_{j i} Z_{j}; # canonicalise(_) # # # Test 41: upper/lower indices. # # # @reset. # {m,n}::Indices. # obj41:= R^{m}_{m} - R_{m}^{m}; # canonicalise(_) # tst41:= # # # Test 42: like in xAct # # # @reset. # {a,b}::Indices(position=fixed). # K^{a}_{a}; # canonicalise(_) # # K_{a}^{a}; # canonicalise(_) # # @reset. # {a,b}::Indices(position=fixed). # K_{a}^{a}_{b}^{b}; # canonicalise(_) # # K^{a}_{a}_{b}^{b}; # canonicalise(_) # More upper/lower indices def test42(): __cdbkernel__=create_scope() {n,r}::Indices(position=fixed). \delta{#}::KroneckerDelta. \partial{#}::PartialDerivative. obj42:= \delta_{n}^{r} \partial_{r}{ u^{n} }; canonicalise(_) tst42:= \delta^{n}_{r} * \partial_{n}{u^{r}} - @(obj42); collect_terms(_) assert(tst42==0) print('Test 42 passed') test42() # Prevent raising/lowering if there is a Derivative in the way. For # the time being we do not even allow this if there is a full # covariant derivative. def test43(): __cdbkernel__=create_scope() {m,n,k}::Indices(position=fixed). \partial{#}::Derivative. obj43:= A_{m n} \partial_{k}{ B^{m n} }; canonicalise(_) tst43:= A_{m n} \partial_{k}{ B^{m n} } - @(obj43); collect_terms(_) assert(tst43==0) print('Test 43 passed') test43() def test45(): __cdbkernel__=create_scope() {\mu,\nu,\rho,\sigma,\kappa,\lambda,\eta,\chi#}::Indices(full, position=fixed). {m,n,p,q,r,s,t,u,v,m#}::Indices(subspace, position=fixed, parent=full). \partial{#}::PartialDerivative. g_{\mu? \nu?}::Symmetric. g^{\mu? \nu?}::Symmetric. obj45a:= \partial_{4 n}{ g_{p 4} } g_{m1 m} g^{m1 p}; canonicalise(_) tst45a:= \partial_{4 n}{ g_{4 p} } g_{m}^{m1} g_{m1}^{p} - @(obj45a); collect_terms(_) assert(tst45a==0) obj45b:= \partial_{4 n}{ g_{4 p} } g_{m1 m} g^{m1 p}; canonicalise(_) tst45b:= \partial_{4 n}{ g_{4 p} } g_{m}^{m1} g_{m1}^{p} - @(obj45b); collect_terms(_) assert(tst45b==0) obj45d:= A_{p} \partial_{n}{ g^{m1 p} } B_{m1}; canonicalise(_) tst45d:= A_{m1} \partial_{n}{ g^{m1 p} } B_{p} - @(obj45d); collect_terms(_) assert(tst45d==0) print('Test 45d passed') #test45() # Spinor indices def test46(): __cdbkernel__=create_scope() {a,b,c,d}::Indices. {a,b,c,d}::AntiCommuting. tst46:= \chi_{a} \psi^{a} + \chi^{a} \psi_{a}; canonicalise(_) collect_terms(_) assert(tst46==0) print('Test 46 passed') test46() # Regression in 1.21 def test47(): __cdbkernel__=create_scope() B0:=B_{\alpha \mu} -> \Delta_{\mu \nu}*F_{\theta \alpha}; canonicalise(_) print('Test 47 passed') test47() # Test 48: derivatives without arguments # #@reset. #\nabla{#}::PartialDerivative. #A_{m} \nabla_{n} B_{k}; #canonicalise(_) def test48(): __cdbkernel__=create_scope() {q,r}::Indices(full). {i,j}::Indices("subspace1", parent=full). {A,B}::Indices("subspace2", parent=full). obj49:= X_{q r} Y_{q r}; split_index(_, $ {q, i, A} $, repeat=True) canonicalise(_) tst49:= X_{i j} Y_{i j} + X_{i A} Y_{i A} + X_{A i} Y_{A i} + X_{A B} Y_{A B} - @(obj49); assert(tst49==0) print('Test 48 passed') test48() def test49(): __cdbkernel__=create_scope() A_{m? n?}::AntiSymmetric. ex:=A_{4 m} + A_{m 4}; canonicalise(_) assert(ex==0) print('Test 49 passed') test49() def test50(): __cdbkernel__=create_scope() {m,n,p}::Indices(position=free); A_{m n}::Symmetric; ex:=\int{ A_{m n} (A^{n m} + A^{m n}) }{x}; canonicalise(_) tst:= 2 \int{ A_{m n} A^{m n} }{x} - @(ex); assert(tst==0) print('Test 50 passed') test50() def test51(): # Sorting should be such that we first sort based on # argument names, and then on index positions. Only # then will canonicalise be able to collect terms # which differ by index raising/lowering. __cdbkernel__=create_scope() {a,b,c}::Indices(position=fixed); ex:= A^{a}(A) A_{a}(B) + A^{a}(B) A_{a}(A); sort_product(_) canonicalise(_) tst:= 2 A^{a}(A) A_{a}(B) - @(ex); assert(tst==0) print('Test 51 passed') test51() def test52(): __cdbkernel__=create_scope() {a,b,c}::Indices(position=fixed); D{#}::Derivative; ex:= D^{a}{A} D_{a}{B} + D^{a}{B} D_{a}{A}; sort_product(_) canonicalise(_) tst:= 2 D^{a}{A} D_{a}{B} - @(ex); assert(tst==0) print('Test 52 passed') test52() # def test53(): # # {a,b,c}::Indices(position=fixed); # D{#}::Derivative; # ex:= D^{a}{A_{b}} D_{a}{B} + D^{a}{B} D_{a}{A}; # sort_product(_); # canonicalise(_); def test53(): __cdbkernel__=create_scope() {m,n}::Integer; {m,n}::Indices(values={1,2,3}); A_{m n}::Symmetric; ex:= A_{1 2} - A_{2 1}; canonicalise(_) assert(ex==0) print("Test 53a passed") ex:= A_{m n} - A_{n m}; canonicalise(_) assert(ex==0) print("Test 53b passed") test53() def test54(): __cdbkernel__=create_scope() {m,n,p,q}::Integer; {m,n,p,q}::Indices(values={0,1,2,3}); R_{m n p q}::RiemannTensor; ex:=R_{3 m 3 3}; canonicalise(_) assert(ex==0) print("Test 54a passed") ex:=R_{3 3 m 3}; canonicalise(_) assert(ex==0) print("Test 54b passed") test54() def test55(): __cdbkernel__=create_scope() {m,n,p,q}::Indices(values={1,2,3,4}); R_{m n p q}::RiemannTensor; ex:=R_{3 m 4 3}; canonicalise(_) tst:= - R_{4 3 m 3} - @(ex); assert(tst==0) ex:= R_{3 4 3 m}; canonicalise(_) tst:= R_{4 3 m 3} - @(ex); assert(tst==0) print("Test 55 passed") test55() def test56(): __cdbkernel__=create_scope() {t,r}::Coordinate; {a,b}::Indices(values={t,r},position=fixed); A_{a b}::Diagonal; ex:=A_{t r} + A_{r r} + A_{r t} + A_{t t}; assert(str(ex)=='A_{r r} + A_{t t}') print("Test 56 passed") test56() def test57(): __cdbkernel__=create_scope() # as above, but without the coordinate property A{#}::Diagonal; ex:=A_{t r}; assert(ex != 0) assert(str(ex)=='A_{t r}') print("Test 57 passed") test57() def test58(): # https://cadabra.science/qa/2291/sort_product-with-position-independent-identical-variables {a,b,c,d,e,f,g}::Indices(vector,position=independent). ex := x_{a} x^{b} - x^{b} x_{a}; sort_product(_); assert(ex==0) print("Test 58 passed") test58()