# 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()
      
