From f1f02f2c7e02ea413d278c912ded40a2b9f48a21 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 23 Nov 2012 21:59:00 +0545 Subject: [PATCH 01/47] typo edit --- sympy/combinatorics/tensor_can.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy/combinatorics/tensor_can.py b/sympy/combinatorics/tensor_can.py index 873956ea3424..1e10d7feca1c 100644 --- a/sympy/combinatorics/tensor_can.py +++ b/sympy/combinatorics/tensor_can.py @@ -635,7 +635,7 @@ def canonicalize(g, dummies, msym, *v): in case of failure, canonicalize_naive is used, which is much slower. - n_i number ot tensors of type `i`. + n_i number of tensors of type `i`. sym_i symmetry under exchange of component tensors of type `i`. From 9b28036ffd62ecaddfe7f240ddfe4b3a9b619423 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 23 Nov 2012 20:33:55 +0545 Subject: [PATCH 02/47] pep8 tensor_can --- sympy/combinatorics/tensor_can.py | 122 +++++++++++++++++------------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/sympy/combinatorics/tensor_can.py b/sympy/combinatorics/tensor_can.py index 1e10d7feca1c..7a52e5f91302 100644 --- a/sympy/combinatorics/tensor_can.py +++ b/sympy/combinatorics/tensor_can.py @@ -1,9 +1,9 @@ from sympy.combinatorics.permutations import Permutation, _af_rmul, _af_rmuln,\ - _af_invert, _af_new + _af_invert, _af_new from sympy.combinatorics.perm_groups import PermutationGroup, _orbit, \ - _orbit_transversal + _orbit_transversal from sympy.combinatorics.util import _distribute_gens_by_base, \ - _orbits_transversals_from_bsgs + _orbits_transversals_from_bsgs """ References for tensor canonicalization: @@ -42,26 +42,29 @@ def dummy_sgs(dummies, sym, n): ======== >>> from sympy.combinatorics.tensor_can import dummy_sgs >>> dummy_sgs([2,3,4,5,6,7], 0, 8) - [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9], [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]] + [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9], + [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]] """ assert len(dummies) <= n res = [] # exchange of contravariant and covariant indices - if sym != None: + if sym is not None: for j in dummies[::2]: - a = range(n+2) + a = range(n + 2) if sym == 1: - a[n] = n+1 - a[n+1] = n - a[j], a[j+1] = a[j+1], a[j] + a[n] = n + 1 + a[n + 1] = n + a[j], a[j + 1] = a[j + 1], a[j] res.append(a) # rename dummy indices for j in dummies[:-3:2]: - a = range(n+2) - a[j:j+4] = a[j+2],a[j+3],a[j],a[j+1] + a = range(n + 2) + a[j:j + 4] = a[j + 2], a[j + 3], a[j], a[j + 1] res.append(a) return res + def _min_dummies(dummies, sym, indices): """ Return list of minima of the orbits of indices in group of dummies @@ -102,6 +105,7 @@ def _trace_S(s, j, b, S_cosets): return h return None + def _trace_D(gj, p_i, Dxtrav): """ Return the representative h satisfying h[gj] == p_i @@ -113,6 +117,7 @@ def _trace_D(gj, p_i, Dxtrav): return h return None + def _dumx_remove(dumx, dumx_flat, p0): """ remove p0 from dumx @@ -124,15 +129,16 @@ def _dumx_remove(dumx, dumx_flat, p0): continue k = dx.index(p0) if k % 2 == 0: - p0_paired = dx[k+1] + p0_paired = dx[k + 1] else: - p0_paired = dx[k-1] + p0_paired = dx[k - 1] dx.remove(p0) dx.remove(p0_paired) dumx_flat.remove(p0) dumx_flat.remove(p0_paired) res.append(dx) + def transversal2coset(size, base, transversal): a = [] j = 0 @@ -142,10 +148,10 @@ def transversal2coset(size, base, transversal): j += 1 else: a.append([range(size)]) - j = len(a)-1 + j = len(a) - 1 while a[j] == [range(size)]: j -= 1 - return a[:j+1] + return a[:j + 1] def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): @@ -156,7 +162,7 @@ def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): list of lists of dummy indices, one list for each type of index; the dummy indices are put in order contravariant, covariant - [d0, -d0, d1,-d1,...]. + [d0, -d0, d1, -d1, ...]. sym list of the symmetries of the index metric for each type. @@ -379,7 +385,7 @@ def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): g = g.array_form num_dummies = size - 2 indices = range(num_dummies) - all_metrics_with_sym = all([_ != None for _ in sym]) + all_metrics_with_sym = all([_ is not None for _ in sym]) num_types = len(sym) dumx = dummies[:] dumx_flat = [] @@ -392,7 +398,7 @@ def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): # strong generating set for D dsgsx = [] for i in range(num_types): - dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies)) + dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies)) ginv = _af_invert(g) idn = range(size) # TAB = list of entries (s, d, h) where h = _af_rmuln(d,g,s) @@ -410,24 +416,25 @@ def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): if all_metrics_with_sym: md = _min_dummies(dumx, sym, indices) else: - md = [min(_orbit(size, [_af_new(ddx) for ddx in dsgsx], ii)) for ii in range(size-2)] + md = [min(_orbit(size, [_af_new( + ddx) for ddx in dsgsx], ii)) for ii in range(size - 2)] - p_i = min([min([md[h[x]] for x in deltab]) for s,d,h in TAB]) + p_i = min([min([md[h[x]] for x in deltab]) for s, d, h in TAB]) dsgsx1 = [_af_new(_) for _ in dsgsx] Dxtrav = _orbit_transversal(size, dsgsx1, p_i, False, af=True) \ - if dsgsx else None + if dsgsx else None if Dxtrav: Dxtrav = [_af_invert(x) for x in Dxtrav] # compute the orbit of p_i for ii in range(num_types): if p_i in dumx[ii]: # the orbit is made by all the indices in dum[ii] - if sym[ii] != None: + if sym[ii] is not None: deltap = dumx[ii] else: # the orbit is made by all the even indices if p_i # is even, by all the odd indices if p_i is odd - p_i_index = dumx[ii].index(p_i)%2 + p_i_index = dumx[ii].index(p_i) % 2 deltap = dumx[ii][p_i_index::2] break else: @@ -492,7 +499,7 @@ def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): # if TAB contains equal permutations up to the sign, return 0 TAB1.sort(key=lambda x: x[-1]) nTAB1 = len(TAB1) - prev = [0]*size + prev = [0] * size while TAB1: s, d, h = TAB1.pop() if h[:-2] == prev[:-2]: @@ -586,17 +593,18 @@ def _get_map_slots(size, fixed_slots): res = range(size) pos = 0 for i in range(size): - if i in fixed_slots: - continue - res[i] = pos - pos += 1 + if i in fixed_slots: + continue + res[i] = pos + pos += 1 return res + def _lift_sgens(size, fixed_slots, free, s): a = [] j = k = 0 fd = zip(fixed_slots, free) - fd = [y for x,y in sorted(fd)] + fd = [y for x, y in sorted(fd)] num_free = len(free) for i in range(size): if i in fixed_slots: @@ -607,6 +615,7 @@ def _lift_sgens(size, fixed_slots, free, s): j += 1 return a + def canonicalize(g, dummies, msym, *v): """ canonicalize tensor formed by tensors @@ -733,10 +742,11 @@ def canonicalize(g, dummies, msym, *v): num_types = 1 else: num_types = len(msym) - if not all(msymx in [0,1,None] for msymx in msym): + if not all(msymx in [0, 1, None] for msymx in msym): raise ValueError('msym entries must be 0, 1 or None') if len(dummies) != num_types: - raise ValueError('dummies and msym must have the same number of elements') + raise ValueError( + 'dummies and msym must have the same number of elements') size = g.size num_tensors = 0 v1 = [] @@ -751,7 +761,7 @@ def canonicalize(g, dummies, msym, *v): can = canonicalize_naive(g, dummies, msym, *v) return can base_i, gens_i = mbsgs - v1.append((base_i, gens_i, [[]]*n_i, sym_i)) + v1.append((base_i, gens_i, [[]] * n_i, sym_i)) num_tensors += n_i if num_types == 1: @@ -767,8 +777,9 @@ def canonicalize(g, dummies, msym, *v): # slot symmetry of the tensor size1, sbase, sgens = gens_products(*v1) if size != size1: - raise ValueError('g has size %d, generators have size %d' %(size, size1)) - free = [i for i in range(size-2) if i not in flat_dummies] + raise ValueError( + 'g has size %d, generators have size %d' % (size, size1)) + free = [i for i in range(size - 2) if i not in flat_dummies] num_free = len(free) # g1 minimal tensor under slot symmetry @@ -776,7 +787,7 @@ def canonicalize(g, dummies, msym, *v): if not flat_dummies: return g1 # save the sign of g1 - sign = 0 if g1[-1] == size-1 else 1 + sign = 0 if g1[-1] == size - 1 else 1 # the free indices are kept fixed. # Determine free_i, the list of slots of tensors which are fixed @@ -817,7 +828,8 @@ def canonicalize(g, dummies, msym, *v): dummies_red = [[x - num_free for x in y] for y in dummies] transv_red = get_transversals(sbase_red, sgens_red) g1_red = _af_new(g1_red) - g2 = double_coset_can_rep(dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red) + g2 = double_coset_can_rep( + dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red) if g2 == 0: return 0 # lift to the case with the free indices @@ -849,7 +861,8 @@ def perm_af_direct_product(gens1, gens2, signed=True): start = list(range(n1)) end = list(range(n1, n1 + n2)) if signed: - gens1 = [gen[:-2] + end + [gen[-2]+n2, gen[-1]+n2] for gen in gens1] + gens1 = [gen[:-2] + end + [gen[-2] + n2, gen[-1] + n2] + for gen in gens1] gens2 = [start + [x + n1 for x in gen] for gen in gens2] else: gens1 = [gen + end for gen in gens1] @@ -859,6 +872,7 @@ def perm_af_direct_product(gens1, gens2, signed=True): return res + def bsgs_direct_product(base1, gens1, base2, gens2, signed=True): """ direct product of two BSGS @@ -895,6 +909,7 @@ def bsgs_direct_product(base1, gens1, base2, gens2, signed=True): gens = [id_af] return base, [_af_new(h) for h in gens] + def get_symmetric_group_sgs(n, sym=0): """ Return base, gens of the minimal BSGS for (anti)symmetric group @@ -910,16 +925,17 @@ def get_symmetric_group_sgs(n, sym=0): """ if n == 1: return [], [_af_new(range(3))] - gens = [Permutation(n-1)(i, i+1)._array_form for i in range(n-1)] + gens = [Permutation(n - 1)(i, i + 1)._array_form for i in range(n - 1)] if sym == 0: - gens = [x + [n, n+1] for x in gens] + gens = [x + [n, n + 1] for x in gens] else: - gens = [x + [n+1, n] for x in gens] - base = range(n-1) + gens = [x + [n + 1, n] for x in gens] + base = range(n - 1) return base, [_af_new(h) for h in gens] -riemann_bsgs = [0, 2], [Permutation(0,1)(4,5), Permutation(2,3)(4,5), \ - Permutation(5)(0,2)(1,3)] +riemann_bsgs = [0, 2], [Permutation(0, 1)(4, 5), Permutation(2, 3)(4, 5), + Permutation(5)(0, 2)(1, 3)] + def get_transversals(base, gens): """ @@ -927,10 +943,10 @@ def get_transversals(base, gens): """ if not base: return [] - stabs = _distribute_gens_by_base(base, gens) + stabs = _distribute_gens_by_base(base, gens) orbits, transversals = _orbits_transversals_from_bsgs(base, stabs) - transversals = [dict((x, h._array_form) for x, h in y.items()) for y in \ - transversals] + transversals = [dict((x, h._array_form) for x, h in y.items()) for y in + transversals] return transversals @@ -959,6 +975,7 @@ def _is_minimal_bsgs(base, gens): sgs1 = [h for h in sgs1 if h._array_form[i] == i] return base1 == base + def get_minimal_bsgs(base, gens): """ Compute a minimal GSGS @@ -987,6 +1004,7 @@ def get_minimal_bsgs(base, gens): return None return base, gens + def tensor_gens(base, gens, list_free_indices, sym=0): """ Returns size, res_base, res_gens BSGS for n tensors of the same type @@ -1036,7 +1054,7 @@ def _get_bsgs(G, base, gens, free_indices): if not base and list_free_indices.count([]) < 2: n = len(list_free_indices) size = gens[0].size - size = n*(gens[0].size - 2) + 2 + size = n * (gens[0].size - 2) + 2 return size, [], [_af_new(range(size))] # if any(list_free_indices) one needs to compute the pointwise @@ -1058,14 +1076,14 @@ def _get_bsgs(G, base, gens, free_indices): for i in range(1, len(list_free_indices)): base1, gens1 = _get_bsgs(G, base, gens, list_free_indices[i]) res_base, res_gens = bsgs_direct_product(res_base, res_gens, - base1, gens1, 1) + base1, gens1, 1) if not list_free_indices[i]: - no_free.append(range(size-2, size - 2 + num_indices)) + no_free.append(range(size - 2, size - 2 + num_indices)) size += num_indices nr = size - 2 res_gens = [h for h in res_gens if h._array_form != id_af] # if sym there are no commuting tensors stop here - if sym == None or not no_free: + if sym is None or not no_free: if not res_gens: res_gens = [_af_new(id_af)] return size, res_base, res_gens @@ -1085,7 +1103,7 @@ def _get_bsgs(G, base, gens, free_indices): a.extend(ind2) a.extend(ind1) base_comm.append(ind1[0]) - a.extend(range(ind2[-1]+1, nr)) + a.extend(range(ind2[-1] + 1, nr)) if sym == 0: a.extend([nr, nr + 1]) else: @@ -1130,7 +1148,7 @@ def gens_products(*v): for i in range(1, len(v)): size, base, gens = tensor_gens(*v[i]) res_base, res_gens = bsgs_direct_product(res_base, res_gens, base, - gens, 1) + gens, 1) res_size = res_gens[0].size id_af = range(res_size) res_gens = [h for h in res_gens if h != id_af] From b04cb7900469bbced3699f27396b53ba8ff283f6 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 23 Nov 2012 21:57:18 +0545 Subject: [PATCH 03/47] reformat for sphinx --- sympy/combinatorics/tensor_can.py | 85 ++++++++++++++++++------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/sympy/combinatorics/tensor_can.py b/sympy/combinatorics/tensor_can.py index 7a52e5f91302..c8af5eb49635 100644 --- a/sympy/combinatorics/tensor_can.py +++ b/sympy/combinatorics/tensor_can.py @@ -28,20 +28,25 @@ def dummy_sgs(dummies, sym, n): """ Return the strong generators for dummy indices - dummies list of dummy indices, - `dummies[2k], dummies[2k+1]` are paired indices - sym symmetry under interchange of contracted dummies - sym = None no symmetry - 0 symmetric - 1 antisymmetric - n number of indices + Parameters + ========== + + dummies : list of dummy indices + `dummies[2k], dummies[2k+1]` are paired indices + sym : symmetry under interchange of contracted dummies:: + * None no symmetry + * 0 commuting + * 1 anticommuting + + n : number of indices in base form the dummy indices are always in consecutive positions Examples ======== + >>> from sympy.combinatorics.tensor_can import dummy_sgs - >>> dummy_sgs([2,3,4,5,6,7], 0, 8) + >>> dummy_sgs(range(2, 8), 0, 8) [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9], [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]] @@ -73,8 +78,9 @@ def _min_dummies(dummies, sym, indices): Examples ======== + >>> from sympy.combinatorics.tensor_can import _min_dummies - >>> _min_dummies([[2,3,4,5,6,7]], [0], range(10)) + >>> _min_dummies([range(2, 8)], [0], range(10)) [0, 1, 2, 2, 2, 2, 2, 2, 8, 9] """ num_types = len(sym) @@ -535,6 +541,7 @@ def canonical_free(base, gens, g, num_free): Examples ======== + >>> from sympy.combinatorics import Permutation >>> from sympy.combinatorics.tensor_can import canonical_free >>> gens = [[1,0,2,3,5,4], [2,3,0,1,4,5],[0,1,3,2,5,4]] @@ -620,40 +627,44 @@ def canonicalize(g, dummies, msym, *v): """ canonicalize tensor formed by tensors - g permutation representing the tensor - - dummies list representing the dummy indices - it can be a list of dummy indices of the same type - or a list of lists of dummy indices, one list for each - type of index; - the dummy indices must come after the free indices, - and put in order contravariant, covariant - [d0, -d0, d1,-d1,...] + Parameters + ========== - msym symmetry of the metric(s) - it can be an integer or a list; - in the first case it is the symmetry of the dummy index metric; - in the second case it is the list of the symmetries of the - index metric for each type + g : permutation representing the tensor - v is a list of (base_i, gens_i, n_i, sym_i) for tensors of type `i` - base_i, gens_i BSGS for tensors of this type. + dummies : list representing the dummy indices + it can be a list of dummy indices of the same type + or a list of lists of dummy indices, one list for each + type of index; + the dummy indices must come after the free indices, + and put in order contravariant, covariant + [d0, -d0, d1,-d1,...] + msym : symmetry of the metric(s) + it can be an integer or a list; + in the first case it is the symmetry of the dummy index metric; + in the second case it is the list of the symmetries of the + index metric for each type + v : list, (base_i, gens_i, n_i, sym_i) for tensors of type `i` - The BSGS should have minimal base under lexicographic ordering; - if not, an attempt is made do get the minimal BSGS; - in case of failure, - canonicalize_naive is used, which is much slower. + base_i, gens_i : BSGS for tensors of this type. + The BSGS should have minimal base under lexicographic ordering; + if not, an attempt is made do get the minimal BSGS; + in case of failure, + canonicalize_naive is used, which is much slower. - n_i number of tensors of type `i`. + n_i : number of tensors of type `i`. - sym_i symmetry under exchange of component tensors of type `i`. + sym_i : symmetry under exchange of component tensors of type `i`. - Both for msym and sym_i the cases are + Both for msym and sym_i the cases are * None no symmetry * 0 commuting * 1 anticommuting - Return 0 if the tensor is zero, else return the array form of + Returns + ======= + + 0 if the tensor is zero, else return the array form of the permutation representing the canonical form of the tensor. Algorithm @@ -686,7 +697,7 @@ def canonicalize(g, dummies, msym, *v): g = [1,3,0,5,4,2,6,7] - T_c = 0 + `T_c = 0` >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize, bsgs_direct_product >>> from sympy.combinatorics import Permutation @@ -701,7 +712,7 @@ def canonicalize(g, dummies, msym, *v): `T_c = -A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2}` - `can = [0,2,1,4,3,5,7,6]` + can = [0,2,1,4,3,5,7,6] >>> t1 = (base2a, gens2a, 2, 1) >>> canonicalize(g, range(6), 0, t0, t1) @@ -843,6 +854,7 @@ def perm_af_direct_product(gens1, gens2, signed=True): Examples ======== + >>> from sympy.combinatorics.tensor_can import perm_af_direct_product >>> gens1 = [[1,0,2,3], [0,1,3,2]] >>> gens2 = [[1,0]] @@ -887,6 +899,7 @@ def bsgs_direct_product(base1, gens1, base2, gens2, signed=True): Examples ======== + >>> from sympy.combinatorics import Permutation >>> from sympy.combinatorics.tensor_can import (get_symmetric_group_sgs, bsgs_direct_product) >>> Permutation.print_cyclic = True @@ -917,6 +930,7 @@ def get_symmetric_group_sgs(n, sym=0): Examples ======== + >>> from sympy.combinatorics import Permutation >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs >>> Permutation.print_cyclic = True @@ -958,6 +972,7 @@ def _is_minimal_bsgs(base, gens): Examples ======== + >>> from sympy.combinatorics import Permutation >>> from sympy.combinatorics.tensor_can import riemann_bsgs, _is_minimal_bsgs >>> _is_minimal_bsgs(*riemann_bsgs) From a5df0646f84d75a518363d66a90ac8b794da1189 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 11 Dec 2012 09:55:00 +0100 Subject: [PATCH 04/47] Fixed a bug in canonicalize; corrected comments in test_tensor_can.py --- sympy/combinatorics/tensor_can.py | 2 +- sympy/combinatorics/tests/test_tensor_can.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/sympy/combinatorics/tensor_can.py b/sympy/combinatorics/tensor_can.py index c8af5eb49635..c122c79c2edd 100644 --- a/sympy/combinatorics/tensor_can.py +++ b/sympy/combinatorics/tensor_can.py @@ -775,7 +775,7 @@ def canonicalize(g, dummies, msym, *v): v1.append((base_i, gens_i, [[]] * n_i, sym_i)) num_tensors += n_i - if num_types == 1: + if num_types == 1 and not isinstance(msym, list): dummies = [dummies] msym = [msym] flat_dummies = [] diff --git a/sympy/combinatorics/tests/test_tensor_can.py b/sympy/combinatorics/tests/test_tensor_can.py index 9f7912773bfd..0c71470cf5e3 100644 --- a/sympy/combinatorics/tests/test_tensor_can.py +++ b/sympy/combinatorics/tests/test_tensor_can.py @@ -305,12 +305,11 @@ def test_canonicalize1(): # g = [10,4,8, 0,7,9, 6,11,1, 2,3,5, 12,13] # T_c = A^{a0 d0 d1}*A^a1_d0^d2*A^{a2 a3 d3}*A_{d1 d2 d3} # can = [0,4,6, 1,5,8, 2,3,10, 7,9,11, 12,13] - base3, gens3 = get_symmetric_group_sgs(3) g = Permutation([10,4,8, 0,7,9, 6,11,1, 2,3,5, 12,13]) can = canonicalize(g, range(4,12), 0, (base3, gens3, 4, 0)) assert can == [0,4,6, 1,5,8, 2,3,10, 7,9,11, 12,13] - # A commuting symmetric, B anticommuting + # A commuting symmetric, B antisymmetric # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # ord = [d0,-d0,d1,-d1,d2,-d2,d3,-d3] # g = [0,2,4,5,7,3,1,6,8,9] @@ -327,14 +326,15 @@ def test_canonicalize1(): # can = [0,2,4, 1,3,6, 5,7, 8,9] can = canonicalize(g, range(8), 0, (base3, gens3,2,1), (base2a,gens2a,1,0)) assert can == [0,2,4, 1,3,6, 5,7, 8,9] - # A anticommuting symmetric, B anticommuting, antisymmetric metric + # A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} # can = [0,2,4, 1,3,6, 5,7, 9,8] can = canonicalize(g, range(8), 1, (base3, gens3,2,1), (base2a,gens2a,1,0)) assert can == [0,2,4, 1,3,6, 5,7, 9,8] - # A anticommuting symmetric, B anticommuting, no metric symmetry + # A anticommuting symmetric, B anticommuting anticommuting, + # no metric symmetry # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 # can = [0,2,4, 1,3,7, 5,6, 8,9] @@ -403,6 +403,8 @@ def test_riemann_invariants(): # R_d11^d1_d0^d5 * R^{d6 d4 d0}_d5 * R_{d7 d2 d8 d9} * # R_{d10 d3 d6 d4} * R^{d2 d7 d11}_d1 * R^{d8 d9 d3 d10} # ord: contravariant d_k ->2*k, covariant d_k -> 2*k+1 + # T_c = R^{d0 d1 d2 d3} * R_{d0 d1}^{d4 d5} * R_{d2 d3}^{d6 d7} * + # R_{d4 d5}^{d8 d9} * R_{d6 d7}^{d10 d11} * R_{d8 d9 d10 d11} g = Permutation([23,2,1,10,12,8,0,11,15,5,17,19,21,7,13,9,4,14,22,3,16,18,6,20,24,25]) can = canonicalize(g, range(24), 0, (baser, gensr, 6, 0)) assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,21,23,24,25] @@ -411,8 +413,6 @@ def test_riemann_invariants(): can = canonicalize(g, range(24), 0, ([2, 0], [Permutation([1,0,2,3,5,4]), Permutation([2,3,0,1,4,5])], 6, 0)) assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,21,23,24,25] - #R^{d0 d1 d2 d3} * R_{d0 d1}^{d4 d5} * R_{d2 d3}^{d6 d7} * - # R_{d4 d5}^{d8 d9} * R_{d6 d7}^{d10 d11} * R_{d8 d9 d10 d11} g = Permutation([0,2,5,7,4,6,9,11,8,10,13,15,12,14,17,19,16,18,21,23,20,22,25,27,24,26,29,31,28,30,33,35,32,34,37,39,36,38,1,3,40,41]) can = canonicalize(g, range(40), 0, (baser, gensr, 10, 0)) assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,24,26,21,23,28,30,25,27,32,34,29,31,36,38,33,35,37,39,40,41] @@ -470,6 +470,8 @@ def test_riemann_products(): # R^{d2 a0 a2 d0} * R^d1_d2^{a1 a3} * R^{a4 a5}_{d0 d1} # ord = [a0,a1,a2,a3,a4,a5,d0,-d0,d1,-d1,d2,-d2] # 0 1 2 3 4 5 6 7 8 9 10 11 + # can = [0, 6, 2, 8, 1, 3, 7, 10, 4, 5, 9, 11, 12, 13] + # T_c = R^{a0 d0 a2 d1}*R^{a1 a3}_d0^d2*R^{a4 a5}_{d1 d2} g = Permutation([10,0,2,6,8,11,1,3,4,5,7,9,12,13]) can = canonicalize(g, range(6,12), 0, (baser, gensr, 3, 0)) assert can == [0, 6, 2, 8, 1, 3, 7, 10, 4, 5, 9, 11, 12, 13] From 36cbcd17166614cf7a17ff197fcbc030890424ec Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 11 Dec 2012 15:20:53 +0100 Subject: [PATCH 05/47] Tensor expressions with canonicalization; riemann_cyclic implements the cyclic symmetry for Riemann tensors. test_tensor.py implements most of the tests in tensor_can.py, and has tests with tensor algebraic expressions. --- sympy/tensor/tensor.py | 967 ++++++++++++++++++++++++++++++ sympy/tensor/tests/test_tensor.py | 522 ++++++++++++++++ 2 files changed, 1489 insertions(+) create mode 100644 sympy/tensor/tensor.py create mode 100644 sympy/tensor/tests/test_tensor.py diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py new file mode 100644 index 000000000000..a4ee5a498937 --- /dev/null +++ b/sympy/tensor/tensor.py @@ -0,0 +1,967 @@ +from collections import defaultdict +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.combinatorics.permutations import Permutation, _af_new, _af_invert +from sympy.core import Basic, Symbol, sympify, Add, Mul, S, Rational +from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, canonical_free, riemann_bsgs + +from functools import wraps + +from sympy.core import S, Symbol, sympify, Tuple, Integer, Basic +from sympy.core.decorators import call_highest_priority +from sympy.core.sympify import SympifyError +from sympy.matrices import ShapeError +from sympy.simplify import simplify +from sympy import cacheit + +class TensorIndexType(object): + def __init__(self, name, metric_sym=0, dummy_fmt=None): + """ + name name of the tensor type + + metric_sym: + 0 symmetric + 1 antisymmetric + None no symmetry + + dummy_fmt name of the head of dummy indices; by default it is + the name of the tensor type + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + """ + self.name = name + self.metric_sym = metric_sym + if not dummy_fmt: + self.dummy_fmt = '%s_%%d' % self.name + else: + self.dummy_fmt = '%s_%%d' % dummy_fmt + + def __str__(self): + return self.name + + __repr__ = __str__ + +class TensorIndex(object): + """ + Tensor indices are contructed with the Einstein summation convention. + + An index can be in contravariant or in covariant form; in the latter + case it is represented prepending a `-` to the index name. + + Dummy indices have the head given by `dummy_fmt` + + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, TensorIndex, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i = TensorIndex('i', Lorentz); i + i + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> A, B = S1('A,B') + >>> A(i)*B(-i) + A(L_0)*B(-L_0) + """ + def __init__(self, name, tensortype, is_contravariant=True): + self.name = name + self.tensortype = tensortype + self.is_contravariant = is_contravariant + + def __str__(self): + s = self.name + if not self.is_contravariant: + s = '-%s' % s + return s + + __repr__ = __str__ + + @cacheit + def covariant(self): + if self.is_contravariant: + return TensorIndex(self.name, self.tensortype, False) + else: + return self + + def __eq__(self, other): + return self.name == other.name and \ + self.tensortype == other.tensortype and \ + self.is_contravariant == other.is_contravariant + + __neg__ = covariant + +def tensor_indices(s, typ): + """ + Returns tensor indices given their name + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + """ + a = s.split(',') + return [TensorIndex(i, typ) for i in a] + + +class TensorSymmetry(object): + """ + Symmetry of a tensor + + bsgs tuple (base, sgs) BSBS of the symmetry of the tensor + + Examples + ======== + + Examples + ======== + + Define a symmetric tensor + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> V = S2('V') + """ + def __init__(self, bsgs): + self.base, self.generators = bsgs + self.rank = self.generators[0].size + +def _get_index_types(indices): + res = [] + for i in indices: + if isinstance(i, TensorIndex): + res.append(i.tensortype) + elif isinstance(i, TensorIndexType): + res.append(i) + else: + raise NotImplementedError + return res + +def has_tensor(p): + if p.is_Tensor: + return True + if p.is_Mul or p.is_Add: + for x in p.args: + if has_tensor(x): + return True + return False + + +class TensorType(Basic): + """ + + Examples + ======== + + Define a symmetric tensor + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> V = S2('V') + """ + is_commutative = False + + def __new__(cls, indices, symmetry, **kw_args): + obj = Basic.__new__(cls, **kw_args) + obj.index_types = [] + obj.index_types = _get_index_types(indices) + obj.types = list(set(obj.index_types)) + obj.types.sort(key=lambda x: x.name) + obj.symmetry = symmetry + assert symmetry.rank == len(indices) + 2 + return obj + + def __str__(self): + return 'TensorType(%s)' %([str(x) for x in self.index_types]) + + def __call__(self, s, commuting=0): + """ + commuting: + None no commutation rule + 0 commutes + 1 anticommutes + + Examples + ======== + + Define symmetric tensors `V`, `W` and `G`, respectively commuting, + anticommuting and with no commutation symmetry + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs, canon_bp + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> V = S2('V') + >>> W = S2('W', 1) + >>> G = S2('G', None) + >>> canon_bp(V(a, b)*V(-b, -a)) + V(L_0, L_1)*V(-L_0, -L_1) + >>> canon_bp(W(a, b)*W(-b, -a)) + 0 + """ + names = s.split(',') + if len(names) == 1: + return TensorHead(names[0], self, commuting) + else: + return [TensorHead(name, self, commuting) for name in names] + +class TensorHead(Basic): + is_commutative = False + + def __new__(cls, name, typ, commuting, **kw_args): + """ + tensor with given name, index types, symmetry, commutation rule + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> A = S2('A') + """ + assert isinstance(name, basestring) + + obj = Basic.__new__(cls, **kw_args) + obj.name = name + obj.index_types = typ.index_types + obj.rank = len(obj.index_types) + obj.types = typ.types + obj.symmetry = typ.symmetry + obj.commuting = commuting + return obj + + def __eq__(self, other): + if not isinstance(other, TensorHead): + return False + return self.name == other.name and self.index_types == other.index_types + + def __neq__(self, other): + return not self == other + + def commutes_with(self, other): + """ + Returns 0 (1) if self and other (anti)commute + Returns None if self and other do not (anti)commute + """ + if self.commuting == None or other.commuting == None: + return None + return self.commuting * other.commuting + + + def __str__(self): + return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) + __repr__ = __str__ + + def __call__(self, *indices): + """ + tensor with indices + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> A = S2('A') + >>> t = A(a, -b) + """ + assert self.rank == len(indices) + components = [self] + free, dum = Tensor.from_indices(indices, self.types) + free.sort(key=lambda x: x[0].name) + dum.sort() + return Tensor(S.One, components, free, dum) + + +class TensExpr(Basic): + """ + A tensor expression is an expression formed by tensors; + currently the sums of tensors are distributed. + + A TensExpr can be a TensAdd or a Tensor. + + TensAdd objects are put in canonic form using the Butler-Portugal + algorithm for canonicalization under monoterm symmetries. + + Tensor objects are formed by products of component tensors, + and include a coefficient, which is a SymPy expression. + """ + + _op_priority = 11.0 + is_Tensor = True + is_TensExpr = True + is_TensMul = False + is_TensAdd = False + is_commutative = False + + def __neg__(self): + return (-1)*self + + def __abs__(self): + raise NotImplementedError + + #@call_highest_priority('__radd__') + def __add__(self, other): + if self.is_TensAdd: + args = self.args + (other,) + return TensAdd(*args) + if other.is_TensAdd: + args = (self,) + other.args + return TensAdd(self, other) + + #@call_highest_priority('__add__') + def __radd__(self, other): + return TensAdd(other, self) + + #@call_highest_priority('__rsub__') + def __sub__(self, other): + return TensAdd(self, -other) + + #@call_highest_priority('__sub__') + def __rsub__(self, other): + return TensAdd(other, -self) + + #@call_highest_priority('__rmul__') + def __mul__(self, other): + #return TensMul(self, other) + if self.is_TensAdd: + return TensAdd(*[x*other for x in self.args]) + if other.is_TensAdd: + return TensAdd(*[self*x for x in other.args]) + return Tensor.__mul__(self, other) + + #@call_highest_priority('__mul__') + def __rmul__(self, other): + if self.is_TensMul: + coeff = other*self.coeff + return Tensor(coeff, self.components, self.free, self.dum) + return TensAdd(*[x*other for x in self.args]) + + #@call_highest_priority('__rpow__') + def __pow__(self, other): + raise NotImplementedError + + #@call_highest_priority('__pow__') + def __rpow__(self, other): + raise NotImplementedError + + #@call_highest_priority('__rdiv__') + def __div__(self, other): + other = sympify(other) + if other.is_Tensor: + raise ValueError('cannot divide by a tensor') + coeff = self.coeff/other + return Tensor(coeff, self.components, self.free, self.dum, is_canon_bp=self._is_canon_bp) + + + #@call_highest_priority('__div__') + def __rdiv__(self, other): + raise NotImplementedError() + + __truediv__ = __div__ + __rtruediv__ = __rdiv__ + + def substitute_indices(self, *index_tuples): + """ + Return a tensor with indices substituted according to `index_tuples` + + Examples + ======== + + from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> A, B = S2('A,B') + >>> t = A(i, k)*B(-k, -j); t + A(i, L_0)*B(-L_0, -j) + >>> t.substitute_indices((i,j), (j, k)) + A(j, L_0)*B(-L_0, -k) + """ + if self.is_TensMul: + free = self.free + free1 = [] + for i, v in index_tuples: + for j, ipos, cpos in free: + if i.name == j.name and i.tensortype == j.tensortype: + if i.is_contravariant == j.is_contravariant: + free1.append((v, ipos, cpos)) + else: + # replace i with an index with the same covariance of i + if j.is_contravariant: + ind = TensorIndex(v.name, v.tensortype) + free1.append((ind, ipos, cpos)) + else: + ind = TensorIndex(v.name, v.tensortype) + ind = -ind + free1.append((ind, ipos, cpos)) + return Tensor(self.coeff, self.components, free1, self.dum) + if self.is_TensAdd: + args = self.args + args1 = [] + for x in args: + y = x.substitute_indices(*indices) + args1.append(y) + return TensAdd(args1) + +class TensAdd(TensExpr): + is_Tensor = True + is_TensAdd = True + + def __new__(cls, *args, **kw_args): + """ + args tuple of addends + """ + args = [sympify(x) for x in args if x] + if not all(x.is_Tensor for x in args): + raise ValueError('all arguments should be tensors') + a = [] + for x in args: + if x.is_TensAdd: + a.extend(list(x.args)) + else: + a.append(x) + args = a + indices = set([x[0] for x in args[0].free]) + if not all(set([y[0] for y in x.free]) == indices for x in args[1:]): + raise ValueError('all tensors must have the same indices') + obj = Basic.__new__(cls, **kw_args) + args.sort(key=lambda x: (x.components, x.free, x.dum)) + a = [] + pprev = None + prev = args[0] + prev_coeff = prev.coeff + changed = False + new = 0 + for x in args[1:]: + # if x and prev have the same tensor, update the coeff of prev + if x.components == prev.components \ + and x.free == prev.free and x.dum == prev.dum: + prev_coeff = prev_coeff + x.coeff + changed = True + op = 0 + else: + # x and prev are different; if not changed, prev has not + # been updated; store it + if not changed: + a.append(prev) + else: + # get a tensor from prev with coeff=prev_coeff and store it + if prev_coeff: + t = Tensor(prev_coeff, prev.components, + prev.free, prev.dum) + a.append(t) + # move x to prev + op = 1 + pprev, prev = prev, x + pprev_coeff, prev_coeff = prev_coeff, x.coeff + changed = False + # if the case op=0 prev was not stored; store it now + # in the case op=1 x was not stored; store it now (as prev) + if op == 0 and prev_coeff: + prev = Tensor(prev_coeff, prev.components, prev.free, prev.dum) + a.append(prev) + elif op == 1: + a.append(prev) + if not a: + return S.Zero + + # TODO introduce option not to use canon_bp automatically in TensAdd + if all(x.is_TensMul for x in a): + a = [x.canon_bp() for x in a] + obj._args = tuple(a) + obj.set_indices = indices + return obj + + def canon_bp(self): + args = [x.canon_bp() for x in self.args] + args.sort(key=lambda x: (x.components, x.free, x.dum)) + return TensAdd(*args) + + def __eq__(self, other): + return self - other == 0 + + def _pretty(self): + a = [] + #args = sorted(self.args) + args = self.args + for x in args: + a.append(str(x)) + + a.sort() + s = ' + '.join(a) + s = s.replace('+ -', '- ') + return s + +class Tensor(TensExpr): + is_Tensor = True + is_TensMul = True + + def __new__(cls, coeff, *args, **kw_args): + """ + + coeff SymPy expression coefficient of the tensor. + + args[0] list of TensorHead of the component tensors. + + args[1] list of (ind, ipos, icomp) + where `ind` is a free index, `ipos` is the slot position + of `ind` in the `icomp`-th component tensor. + + args[2] list of tuples representing dummy indices. + (ipos1, ipos2, icomp1, icomp2) indicates that the contravariant + dummy index is the `ipos1` slot position in the `icomp1`-th + component tensor; the corresponding covariant index is + in the `ipos2` slot position in the `icomp2`-th component tensor. + """ + # composite tensor + # p_i*p_i + # t = Tensor(None, t1, t2) + obj = Basic.__new__(cls) + obj.components = args[0] + obj.types = [] + for t in obj.components: + obj.types.extend(t.types) + obj.free = args[1] + obj.dum = args[2] + obj.rank = len(obj.free) + 2*len(obj.dum) + obj.coeff = coeff + obj._is_canon_bp = kw_args.get('is_canon_bp', False) + + return obj + + def __eq__(self, other): + if not other.is_Tensor : + return False + res = self.components == other.components and \ + self.free == other.free and \ + self.dum == other.dum and self.coeff == other.coeff + return res + + def __neq__(self, other): + return not self == other + + @staticmethod + def from_indices(indices, types): + """ + Returns free, dum for single component tensor + + free list of tuples (index, pos, 0), + where `pos` is the position of index in + the list of indices formed by the component tensors + + dum list of tuples (pos_contr, pos_cov, 0, 0) + + + """ + n = len(indices) + if n == 1: + return [(indices[0], 0, 0)], [] + + # find the positions of the free indices and of the dummy indices + free = [True]*len(indices) + index_dict = {} + dum = [] + for i, index in enumerate(indices): + name = index.name + typ = index.tensortype + contr = index.is_contravariant + if (name, typ) in index_dict: + # found a pair of dummy indices + is_contr, pos = index_dict[(name, typ)] + # check consistency and update free + if is_contr: + if contr: + raise ValueError('two equal contravariant indices in slots %d and %d' %(pos, i)) + else: + free[pos] = False + free[i] = False + else: + if contr: + free[pos] = False + free[i] = False + else: + raise ValueError('two equal covariant indices in slots %d and %d' %(pos, i)) + if contr: + dum.append((i, pos, 0, 0)) + else: + dum.append((pos, i, 0, 0)) + else: + index_dict[(name, typ)] = index.is_contravariant, i + + free_indices = [(index, i, 0) for i, index in enumerate(indices) if free[i]] + free = sorted(free_indices, key=lambda x: (x[0].tensortype, x[0].name)) + + return free, dum + + def get_indices(self): + """ + Returns the list of indices of the tensor + + The indices are listed in the order in which they appear in the + component tensors. + """ + indices = [None]*self.rank + start = 0 + pos = 0 + vpos = [] + components = self.components + for t in self.components: + vpos.append(pos) + pos += t.rank + cdt = defaultdict(int) + for indx, ipos, cpos in self.free: + start = vpos[cpos] + indices[start + ipos] = indx + for ipos1, ipos2, cpos1, cpos2 in self.dum: + start1 = vpos[cpos1] + start2 = vpos[cpos2] + typ1 = components[cpos1].index_types[ipos1] + assert typ1 == components[cpos2].index_types[ipos2] + fmt = typ1.dummy_fmt + nd = cdt[typ1] + indices[start1 + ipos1] = TensorIndex(fmt % nd, typ1) + indices[start2 + ipos2] = TensorIndex(fmt % nd, typ1, False) + cdt[typ1] += 1 + return indices + + def split(self): + """ + Returns a list of tensors, whose proouct is `self` + + Dummy indices contracted among different tensor components + become free indices with the same name as the one used to + represent the dummy indices. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> A, B = S2('A,B') + >>> t = A(a,b)*B(-b,c) + >>> t + A(a, L_0)*B(-L_0, c) + >>> t.split() + [A(a, L_0), B(-L_0, c)] + + + """ + indices = self.get_indices() + pos = 0 + components = self.components + res = [] + for t in self.components: + t1 = t(*indices[pos:pos + t.rank]) + pos += t.rank + res.append(t1) + res[0] = Tensor(self.coeff, res[0].components, res[0].free, res[0].dum, is_canon_bp=res[0]._is_canon_bp) + return res + + def canon_args(self): + """ + Returns (g, dummies, msym, v), the entries of `canonicalize` + + see `canonicalize` in tensor_can.py + """ + # to be called after sorted_components + from sympy.combinatorics.permutations import _af_new + types = list(set(self.types)) + types.sort(key = lambda x: x.name) + n = self.rank + g = [None]*n + [n, n+1] + pos = 0 + vpos = [] + components = self.components + for t in self.components: + vpos.append(pos) + pos += t.rank + for i, (indx, ipos, cpos) in enumerate(self.free): + pos = vpos[cpos] + ipos + g[pos] = i + + pos = len(self.free) + j = len(self.free) + dummies = [] + prev = None + a = [] + msym = [] + for ipos1, ipos2, cpos1, cpos2 in self.dum: + pos1 = vpos[cpos1] + ipos1 + pos2 = vpos[cpos2] + ipos2 + g[pos1] = j + g[pos2] = j + 1 + j += 2 + typ = components[cpos1].index_types[ipos1] + if typ != prev: + if a: + dummies.append(a) + a = [pos, pos + 1] + prev = typ + msym.append(typ.metric_sym) + else: + a.extend([pos, pos + 1]) + pos += 2 + if a: + dummies.append(a) + numtyp = [] + prev = None + for t in components: + if t == prev: + numtyp[-1][1] += 1 + else: + prev = t + numtyp.append([prev, 1]) + v = [] + for h, n in numtyp: + v.append((h.symmetry.base, h.symmetry.generators, n, h.commuting)) + return _af_new(g), dummies, msym, v + + def __mul__(self, other): + #if not isinstance(other, Tensor): + other = sympify(other) + if not other.is_Tensor: + coeff = self.coeff*other + return Tensor(coeff, self.components, self.free, self.dum, is_canon_bp=self._is_canon_bp) + if other.is_TensAdd: + return TensAdd(*[self*x for x in other.args]) + + components = self.components + other.components + # find out which free indices of self and other are contracted + free_dict1 = dict([(i.name, (pos, cpos, i)) for i, pos, cpos in self.free]) + free_dict2 = dict([(i.name, (pos, cpos, i)) for i, pos, cpos in other.free]) + + free_names = set(free_dict1.keys()) & set(free_dict2.keys()) + # find the new `free` and `dum` + nc1 = len(self.components) + dum2 = [(i1, i2, c1 + nc1, c2 + nc1) for i1, i2, c1, c2 in other.dum] + free1 = [(ind, i, c) for ind, i, c in self.free if ind.name not in free_names] + free2 = [(ind, i, c + nc1) for ind, i, c in other.free if ind.name not in free_names] + free = free1 + free2 + dum = self.dum + dum2 + for name in free_names: + ipos1, cpos1, ind1 = free_dict1[name] + ipos2, cpos2, ind2 = free_dict2[name] + cpos2 += nc1 + if ind1.is_contravariant == ind2.is_contravariant: + raise ValueError('wrong index contruction %s' % ind1) + if ind1.is_contravariant: + new_dummy = (ipos1, ipos2, cpos1, cpos2) + else: + new_dummy = (ipos2, ipos1, cpos2, cpos1) + dum.append(new_dummy) + coeff = self.coeff*other.coeff + return Tensor(coeff, components, free, dum) + + + def sorted_components(self): + """ + Returns a tensor with sorted components + """ + cv = zip(self.components, range(len(self.components))) + sign = 1 + n = len(cv) - 1 + for i in range(n): + for j in range(n, i, -1): + c = cv[j-1][0].commutes_with(cv[j][0]) + if c == None: + continue + if (cv[j-1][0].types, cv[j-1][0].name) > \ + (cv[j][0].types, cv[j][0].name): + cv[j-1], cv[j] = cv[j], cv[j-1] + if c: + sign = -sign + # perm_inv[new_pos] = old_pos + components = [x[0] for x in cv] + perm_inv = [x[1] for x in cv] + perm = _af_invert(perm_inv) + free = [(ind, i, perm[c]) for ind, i, c in self.free] + dum = [(i1, i2, perm[c1], perm[c2]) for i1, i2, c1, c2 in self.dum] + free.sort(key = lambda x: (x[0].tensortype, x[0].name)) + dum.sort(key = lambda x: components[x[2]].index_types[x[0]]) + if sign == -1: + coeff = -self.coeff + else: + coeff = self.coeff + t = Tensor(coeff, components, free, dum) + return t + + def perm2tensor(self, g, canon_bp=False): + """ + Returns the tensor corresponding to the permutation `g` + + g permutation corrisponding to the tensor in the representation + used in canonicalization + + canon_bp if canon_bp is True, then `g` is the permutation + corresponding to the canonical form of the tensor + """ + from bisect import bisect_right + vpos = [] + components = self.components + pos = 0 + for t in self.components: + vpos.append(pos) + pos += t.rank + free_indices = [x[0] for x in self.free] + sorted_free = sorted(free_indices, key=lambda x:(x.tensortype, x.name)) + nfree = len(sorted_free) + rank = self.rank + indices = [None]*rank + dum = [[None]*4 for i in range((rank - nfree)//2)] + free = [] + icomp = -1 + for i in range(rank): + if i in vpos: + icomp += 1 + pos0 = i + ipos = i - pos0 + gi = g[i] + if gi < nfree: + ind = sorted_free[gi] + free.append((ind, ipos, icomp)) + else: + j = gi - nfree + idum, cov = divmod(j, 2) + if cov: + dum[idum][1] = ipos + dum[idum][3] = icomp + else: + dum[idum][0] = ipos + dum[idum][2] = icomp + dum = [tuple(x) for x in dum] + coeff = self.coeff + if g[-1] != len(g) - 1: + coeff = -coeff + res = Tensor(coeff, components, free, dum, is_canon_bp=canon_bp) + #if canon_bp: + # res._canon_bp = True + return res + + def canon_bp(self): + """ + canonicalize using the Butler-Portugal algorithm for canonicalization + under monoterm symmetries. + + Examples + ======== + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + >>> S2 = TensorType([Lorentz]*2, sym2a) + >>> A = S2('A') + >>> t = A(m0,-m1)*A(m1,-m2)*A(m2,-m0) + >>> t.canon_bp() + 0 + """ + from sympy.combinatorics.tensor_can import canonicalize + if self._is_canon_bp: + return self + t = self.sorted_components() + g, dummies, msym, v = t.canon_args() + can = canonicalize(g, dummies, msym, *v) + if can == 0: + return S.Zero + return t.perm2tensor(can, True) + + + def _pretty(self): + indices = [str(ind) for ind in self.get_indices()] + pos = 0 + a = [] + for t in self.components: + a.append('%s(%s)' % (t.name, ', '.join(indices[pos:pos + t.rank]))) + pos += t.rank + res = '*'. join(a) + if self.coeff == S.One: + return res + elif self.coeff == -S.One: + return '-%s' % res + if self.coeff.is_Atom: + return '%s*%s' % (self.coeff, res) + else: + return '(%s)*%s' %(self.coeff, res) + + + +def canon_bp(p): + """ + Butler-Portugal canonicalization + """ + if p.is_Tensor: + return p.canon_bp() + if p.is_Add: + return Add(*[canon_bp(x) for x in p.args]) + if p.is_Mul: + return Mul(*[canon_bp(x) for x in p.args]) + return p + +def tensor_mul(*a): + """ + product of tensors + """ + t = a[0] + for tx in a[1:]: + t = t*tx + return t + +def riemann_cyclic_replaceR(t_r): + """ + R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q) + + """ + free = sorted(t_r.free, key=lambda x: x[1]) + m, n, p, q = [x[0] for x in free] + t0 = S(2)/3*t_r + t1 = - S(1)/3*t_r.substitute_indices((m,m),(n,q),(p,n),(q,p)) + t2 = S(1)/3*t_r.substitute_indices((m,m),(n,p),(p,n),(q,q)) + t3 = t0 + t1 + t2 + return t3 + +def riemann_cyclic(t2): + """ + replace each Riemann tensor with an equivalent expression + satisfying the cyclic identity + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, riemann_cyclic, riemann_bsgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> symr = TensorSymmetry(riemann_bsgs) + >>> R4 = TensorType([Lorentz]*4, symr) + >>> R = R4('R') + >>> t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) + >>> riemann_cyclic(t) + 0 + """ + a = t2.args + a1 = [x.split() for x in a] + a2 = [[riemann_cyclic_replaceR(tx) for tx in y] for y in a1] + a3 = [tensor_mul(*v) for v in a2] + t3 = TensAdd(*a3) + if not t3: + return t3 + else: + return canon_bp(t3) diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py new file mode 100644 index 000000000000..856ae0e020ee --- /dev/null +++ b/sympy/tensor/tests/test_tensor.py @@ -0,0 +1,522 @@ +from sympy.core import S, Rational +from sympy.combinatorics import Permutation +from sympy.combinatorics.tensor_can import (bsgs_direct_product, riemann_bsgs) +from sympy.tensor.tensor import (TensorIndexType, tensor_indices, + TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, + tensor_mul, canon_bp, TensAdd, riemann_cyclic_replaceR, riemann_cyclic) + + +def test_get_indices(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([Lorentz]*2, sym2) + A, B = S2('A,B') + t = A(a,b)*B(-b,c) + indices = t.get_indices() + L_0 = TensorIndex('L_0', Lorentz) + assert indices == [a, L_0, -L_0, c] + a = t.split() + t2 = tensor_mul(*a) + assert t == t2 + +def test_canonicalize_no_slot_sym(): + # A_d0 * B^d0; T_c = A^d0*B_d0 + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b, d0, d1 = tensor_indices('a,b,d0,d1', Lorentz) + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym1) + A, B = S1('A,B') + t = A(-d0)*B(d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0)*B(-L_0)' + + # A^a * B^b; T_c = T + t = A(a)*B(b) + tc = t.canon_bp() + assert tc == t + # B^b * A^a + t1 = B(b)*A(a) + tc = t1.canon_bp() + assert str(tc) == 'A(a)*B(b)' + + # A symmetric + # A^{b}_{d0}*A^{d0, a}; T_c = A^{a d0}*A{b}_{d0} + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([Lorentz]*2, sym2) + A = S2('A') + t = A(b, -d0)*A(d0, a) + tc = t.canon_bp() + assert str(tc) == 'A(a, L_0)*A(b, -L_0)' + + # A^{d1}_{d0}*B^d0*C_d1 + # T_c = A^{d0 d1}*B_d0*C_d1 + B, C = S1('B,C') + t = A(d1, -d0)*B(d0)*C(-d1) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_0)*C(-L_1)' + + # A without symmetry + # A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5] + # T_c = A^{d0 d1}*B_d1*C_d0; can = [0,2,3,1,4,5] + nsym2 = TensorSymmetry(([], [Permutation(range(4))])) + NS2 = TensorType([Lorentz]*2, nsym2) + A = NS2('A') + B, C = S1('B,C') + t = A(d1, -d0)*B(d0)*C(-d1) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_1)*C(-L_0)' + + # A, B without symmetry + # A^{d1}_{d0}*B_{d1}^{d0} + # T_c = A^{d0 d1}*B_{d0 d1} + B = NS2('B') + t = A(d1, -d0)*B(-d1, d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_0, -L_1)' + # A_{d0}^{d1}*B_{d1}^{d0} + # T_c = A^{d0 d1}*B_{d1 d0} + t = A(-d0, d1)*B(-d1, d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_1, -L_0)' + + # A, B, C without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} + # T_c=A^{d0 d1}*B_{a d1}*C_{d0 b} + C = NS2('C') + t = A(d1, d0)*B(-a, -d0)*C(-d1, -b) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-a, -L_1)*C(-L_0, -b)' + + # A symmetric, B and C without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} + # T_c = A^{d0 d1}*B_{a d0}*C_{d1 b} + A = S2('A') + t = A(d1, d0)*B(-a, -d0)*C(-d1, -b) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-a, -L_0)*C(-L_1, -b)' + + # A and C symmetric, B without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1] + # T_c = A^{d0 d1}*B_{a d0}*C_{b d1} + C = S2('C') + t = A(d1, d0)*B(-a, -d0)*C(-d1, -b) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-a, -L_0)*C(-b, -L_1)' + +def test_canonicalize_no_dummies(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + + # A commuting + # A^c A^b A^a + # T_c = A^a A^b A^c + S1 = TensorType([Lorentz], sym1) + A = S1('A') + t = A(c)*A(b)*A(a) + tc = t.canon_bp() + assert str(tc) == 'A(a)*A(b)*A(c)' + + # A anticommuting + # A^c A^b A^a + # T_c = -A^a A^b A^c + A = S1('A', 1) + t = A(c)*A(b)*A(a) + tc = t.canon_bp() + assert str(tc) == '-A(a)*A(b)*A(c)' + + # A commuting and symmetric + # A^{b,d}*A^{c,a} + # T_c = A^{a c}*A^{b d} + S2 = TensorType([Lorentz]*2, sym2) + A = S2('A') + t = A(b, d)*A(c, a) + tc = t.canon_bp() + assert str(tc) == 'A(a, c)*A(b, d)' + + # A anticommuting and symmetric + # A^{b,d}*A^{c,a} + # T_c = -A^{a c}*A^{b d} + A = S2('A', 1) + t = A(b, d)*A(c, a) + tc = t.canon_bp() + assert str(tc) == '-A(a, c)*A(b, d)' + + # A^{c,a}*A^{b,d} + # T_c = A^{a c}*A^{b d} + t = A(c, a)*A(b, d) + tc = t.canon_bp() + assert str(tc) == 'A(a, c)*A(b, d)' + +def test_no_metric_symmetry(): + # no metric symmetry; A no symmetry + # A^d1_d0 * A^d0_d1 + # T_c = A^d0_d1 * A^d1_d0 + Lorentz = TensorIndexType('Lorentz', metric_sym=None, dummy_fmt='L') + d0, d1, d2, d3 = tensor_indices('d0,d1,d2,d3', Lorentz) + nsym2 = TensorSymmetry(([], [Permutation(range(4))])) + NS2 = TensorType([Lorentz]*2, nsym2) + A = NS2('A') + t = A(d1, -d0)*A(d0, -d1) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_0)' + + # A^d1_d2 * A^d0_d3 * A^d2_d1 * A^d3_d0 + # T_c = A^d0_d1 * A^d1_d0 * A^d2_d3 * A^d3_d2 + t = A(d1, -d2)*A(d0, -d3)*A(d2,-d1)*A(d3,-d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_0)*A(L_2, -L_3)*A(L_3, -L_2)' + + # A^d0_d2 * A^d1_d3 * A^d3_d0 * A^d2_d1 + # T_c = A^d0_d1 * A^d1_d2 * A^d2_d3 * A^d3_d0 + t = A(d0, -d1)*A(d1, -d2)*A(d2, -d3)*A(d3,-d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_2)*A(L_2, -L_3)*A(L_3, -L_0)' + +def test_canonicalize1(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Lorentz) + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + base3, gens3 = get_symmetric_group_sgs(3) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + sym3 = TensorSymmetry(get_symmetric_group_sgs(3)) + sym3a = TensorSymmetry(get_symmetric_group_sgs(3, 1)) + + # A_d0*A^d0; ord = [d0,-d0] + # T_c = A^d0*A_d0 + S1 = TensorType([Lorentz], sym1) + A = S1('A') + t = A(-d0)*A(d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0)*A(-L_0)' + + # A commuting + # A_d0*A_d1*A_d2*A^d2*A^d1*A^d0 + # T_c = A^d0*A_d0*A^d1*A_d1*A^d2*A_d2 + t = A(-d0)*A(-d1)*A(-d2)*A(d2)*A(d1)*A(d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0)*A(-L_0)*A(L_1)*A(-L_1)*A(L_2)*A(-L_2)' + + # A anticommuting + # A_d0*A_d1*A_d2*A^d2*A^d1*A^d0 + # T_c 0 + A = S1('A', 1) + t = A(-d0)*A(-d1)*A(-d2)*A(d2)*A(d1)*A(d0) + tc = t.canon_bp() + assert tc == 0 + + # A commuting symmetric + # A^{d0 b}*A^a_d1*A^d1_d0 + # T_c = A^{a d0}*A^{b d1}*A_{d0 d1} + S2 = TensorType([Lorentz]*2, sym2) + A = S2('A') + t = A(d0, b)*A(a, -d1)*A(d1, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(a, L_0)*A(b, L_1)*A(-L_0, -L_1)' + + # A, B commuting symmetric + # A^{d0 b}*A^d1_d0*B^a_d1 + # T_c = A^{b d0}*A_d0^d1*B^a_d1 + B = S2('B') + t = A(d0, b)*A(d1, -d0)*B(a, -d1) + tc = t.canon_bp() + assert str(tc) == 'A(b, L_0)*A(-L_0, L_1)*B(a, -L_1)' + + # A commuting symmetric + # A^{d1 d0 b}*A^{a}_{d1 d0}; ord=[a,b, d0,-d0,d1,-d1] + # T_c = A^{a d0 d1}*A^{b}_{d0 d1} + S3 = TensorType([Lorentz]*3, sym3) + A = S3('A') + t = A(d1, d0, b)*A(a, -d1, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(a, L_0, L_1)*A(b, -L_0, -L_1)' + + # A^{d3 d0 d2}*A^a0_{d1 d2}*A^d1_d3^a1*A^{a2 a3}_d0 + # T_c = A^{a0 d0 d1}*A^a1_d0^d2*A^{a2 a3 d3}*A_{d1 d2 d3} + t = A(d3, d0, d2)*A(a0, -d1, -d2)*A(d1, -d3, a1)*A(a2, a3, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(a0, L_0, L_1)*A(a1, -L_0, L_2)*A(a2, a3, L_3)*A(-L_1, -L_2, -L_3)' + + # A commuting symmetric, B antisymmetric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # in this esxample and in the next three, + # renaming dummy indices and using symmetry of A, + # T = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 + # can = 0 + S2a = TensorType([Lorentz]*2, sym2a) + A = S3('A') + B = S2a('B') + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert tc == 0 + + # A anticommuting symmetric, B anticommuting + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} + A = S3('A', 1) + B = S2a('B') + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1, L_2)*A(-L_0, -L_1, L_3)*B(-L_2, -L_3)' + + # A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} + Spinor = TensorIndexType('Spinor', metric_sym=1, dummy_fmt='S') + a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) + S3 = TensorType([Spinor]*3, sym3) + S2a = TensorType([Spinor]*2, sym2a) + A = S3('A', 1) + B = S2a('B') + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert str(tc) == '-A(S_0, S_1, S_2)*A(-S_0, -S_1, S_3)*B(-S_2, -S_3)' + + # A anticommuting symmetric, B antisymmetric anticommuting, + # no metric symmetry + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 + Mat = TensorIndexType('Mat', metric_sym=None, dummy_fmt='M') + a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) + S3 = TensorType([Mat]*3, sym3) + S2a = TensorType([Mat]*2, sym2a) + A = S3('A', 1) + B = S2a('B') + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert str(tc) == 'A(M_0, M_1, M_2)*A(-M_0, -M_1, -M_3)*B(-M_2, M_3)' + + # Gamma anticommuting + # Gamma_{mu nu} * gamma^rho * Gamma^{nu mu alpha} + # T_c = -Gamma^{mu nu} * gamma^rho * Gamma_{alpha mu nu} + S1 = TensorType([Lorentz], sym1) + S2a = TensorType([Lorentz]*2, sym2a) + S3a = TensorType([Lorentz]*3, sym3a) + alpha, beta, gamma, mu, nu, rho = \ + tensor_indices('alpha,beta,gamma,mu,nu,rho', Lorentz) + Gamma = S1('Gamma', None) + Gamma2 = S2a('Gamma', None) + Gamma3 = S3a('Gamma', None) + t = Gamma2(-mu,-nu)*Gamma(rho)*Gamma3(nu, mu, alpha) + tc = t.canon_bp() + assert str(tc) == '-Gamma(L_0, L_1)*Gamma(rho)*Gamma(alpha, -L_0, -L_1)' + + # Gamma_{mu nu} * Gamma^{gamma beta} * gamma_rho * Gamma^{nu mu alpha} + # T_c = Gamma^{mu nu} * Gamma^{beta gamma} * gamma_rho * Gamma^alpha_{mu nu} + t = Gamma2(mu, nu)*Gamma2(beta, gamma)*Gamma(-rho)*Gamma3(alpha, -mu, -nu) + tc = t.canon_bp() + assert str(tc) == 'Gamma(L_0, L_1)*Gamma(beta, gamma)*Gamma(-rho)*Gamma(alpha, -L_0, -L_1)' + + # f^a_{b,c} antisymmetric in b,c; A_mu^a no symmetry + # f^c_{d a} * f_{c e b} * A_mu^d * A_nu^a * A^{nu e} * A^{mu b} + # g = [8,11,5, 9,13,7, 1,10, 3,4, 2,12, 0,6, 14,15] + # T_c = -f^{a b c} * f_a^{d e} * A^mu_b * A_{mu d} * A^nu_c * A_{nu e} + Flavor = TensorIndexType('Flavor', dummy_fmt='F') + a, b, c, d, e, ff = tensor_indices('a,b,c,d,e,f', Flavor) + mu, nu = tensor_indices('mu,nu', Lorentz) + sym_f = TensorSymmetry(bsgs_direct_product(sym1.base, sym1.generators, + sym2a.base, sym2a.generators)) + S_f = TensorType([Flavor]*3, sym_f) + sym_A = TensorSymmetry(bsgs_direct_product(sym1.base, sym1.generators, sym1.base, sym1.generators)) + S_A = TensorType([Lorentz, Flavor], sym_A) + f = S_f('f') + A = S_A('A') + t = f(c, -d, -a)*f(-c, -e, -b)*A(-mu, d)*A(-nu, a)*A(nu, e)*A(mu, b) + tc = t.canon_bp() + assert str(tc) == '-f(F_0, F_1, F_2)*f(-F_0, F_3, F_4)*A(L_0, -F_1)*A(-L_0, -F_3)*A(L_1, -F_2)*A(-L_1, -F_4)' + + +def test_riemann_invariants(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11 = \ + tensor_indices(','.join(['d%d' % i for i in range(12)]), Lorentz) + # R^{d0 d1}_{d1 d0}; ord = [d0,-d0,d1,-d1] + # T_c = -R^{d0 d1}_{d0 d1} + symr = TensorSymmetry(riemann_bsgs) + R4 = TensorType([Lorentz]*4, symr) + R = R4('R', 0) + t = R(d0, d1, -d1, -d0) + tc = t.canon_bp() + assert str(tc) == '-R(L_0, L_1, -L_0, -L_1)' + + # R_d11^d1_d0^d5 * R^{d6 d4 d0}_d5 * R_{d7 d2 d8 d9} * + # R_{d10 d3 d6 d4} * R^{d2 d7 d11}_d1 * R^{d8 d9 d3 d10} + # can = [0,2,4,6, 1,3,8,10, 5,7,12,14, 9,11,16,18, 13,15,20,22, + # 17,19,21 Date: Tue, 11 Dec 2012 15:38:41 +0100 Subject: [PATCH 06/47] Added is_Tensor in basic.py; modified str.py to print tensors --- sympy/combinatorics/tests/test_tensor_can.py | 2 +- sympy/core/basic.py | 1 + sympy/printing/str.py | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/sympy/combinatorics/tests/test_tensor_can.py b/sympy/combinatorics/tests/test_tensor_can.py index 0c71470cf5e3..ab014a38bddc 100644 --- a/sympy/combinatorics/tests/test_tensor_can.py +++ b/sympy/combinatorics/tests/test_tensor_can.py @@ -333,7 +333,7 @@ def test_canonicalize1(): can = canonicalize(g, range(8), 1, (base3, gens3,2,1), (base2a,gens2a,1,0)) assert can == [0,2,4, 1,3,6, 5,7, 9,8] - # A anticommuting symmetric, B anticommuting anticommuting, + # A anticommuting symmetric, B anticommuting anticommuting, # no metric symmetry # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 diff --git a/sympy/core/basic.py b/sympy/core/basic.py index 012ed3632607..fc41e9f41717 100644 --- a/sympy/core/basic.py +++ b/sympy/core/basic.py @@ -70,6 +70,7 @@ class Basic(object): is_Boolean = False is_Not = False is_Matrix = False + is_Tensor = False @property @deprecated(useinstead="is_Float", issue=1721, deprecated_since_version="0.7.0") diff --git a/sympy/printing/str.py b/sympy/printing/str.py index eea89d07479a..cc4ee5a00b25 100644 --- a/sympy/printing/str.py +++ b/sympy/printing/str.py @@ -335,6 +335,12 @@ def _print_Permutation(self, expr): use = trim return 'Permutation(%s)' % use + def _print_Tensor(self, expr): + return expr._pretty() + + def _print_TensAdd(self, expr): + return expr._pretty() + def _print_PermutationGroup(self, expr): p = [' %s' % str(a) for a in expr.args] return 'PermutationGroup([\n%s])' % ',\n'.join(p) From c52481b0f1e4c181e633a610d92396bfae6df5a1 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 15 Dec 2012 09:39:08 +0100 Subject: [PATCH 07/47] Added metric and dim attributes to TensorIndexType; added contract_metric; added tests --- sympy/tensor/tensor.py | 212 +++++++++++++++++++++++++++--- sympy/tensor/tests/test_tensor.py | 102 +++++++++++++- 2 files changed, 294 insertions(+), 20 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index a4ee5a498937..9168865a1583 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -14,7 +14,7 @@ from sympy import cacheit class TensorIndexType(object): - def __init__(self, name, metric_sym=0, dummy_fmt=None): + def __init__(self, name, metric_sym=0, dim=None, dummy_fmt=None): """ name name of the tensor type @@ -38,6 +38,18 @@ def __init__(self, name, metric_sym=0, dummy_fmt=None): self.dummy_fmt = '%s_%%d' % self.name else: self.dummy_fmt = '%s_%%d' % dummy_fmt + self.metric = self.get_metric() + self.dim = dim + + def get_metric(self): + if self.metric_sym is None: + return None + sym2 = TensorSymmetry(get_symmetric_group_sgs(2, self.metric_sym)) + S2 = TensorType([self]*2, sym2) + metric = S2('metric') + return metric + + def __str__(self): return self.name @@ -92,7 +104,14 @@ def __eq__(self, other): self.tensortype == other.tensortype and \ self.is_contravariant == other.is_contravariant - __neg__ = covariant + def __neq__(self, other): + return not (self == other) + + @cacheit + def __neg__(self): + t1 = TensorIndex(self.name, self.tensortype, + (not self.is_contravariant)) + return t1 def tensor_indices(s, typ): """ @@ -255,9 +274,15 @@ def commutes_with(self, other): Returns 0 (1) if self and other (anti)commute Returns None if self and other do not (anti)commute """ - if self.commuting == None or other.commuting == None: - return None - return self.commuting * other.commuting + if self.commuting == 0 or other.commuting == 0: + return 0 + if self.commuting == 1 and other.commuting == 1: + return 1 + return None + + #if self.commuting == None or other.commuting == None: + # return None + #return self.commuting * other.commuting def __str__(self): @@ -316,11 +341,10 @@ def __abs__(self): #@call_highest_priority('__radd__') def __add__(self, other): + other = sympify(other) if self.is_TensAdd: args = self.args + (other,) return TensAdd(*args) - if other.is_TensAdd: - args = (self,) + other.args return TensAdd(self, other) #@call_highest_priority('__add__') @@ -329,6 +353,7 @@ def __radd__(self, other): #@call_highest_priority('__rsub__') def __sub__(self, other): + other = sympify(other) return TensAdd(self, -other) #@call_highest_priority('__sub__') @@ -416,9 +441,9 @@ def substitute_indices(self, *index_tuples): args = self.args args1 = [] for x in args: - y = x.substitute_indices(*indices) + y = x.substitute_indices(*index_tuples) args1.append(y) - return TensAdd(args1) + return TensAdd(*args1) class TensAdd(TensExpr): is_Tensor = True @@ -430,7 +455,15 @@ def __new__(cls, *args, **kw_args): """ args = [sympify(x) for x in args if x] if not all(x.is_Tensor for x in args): - raise ValueError('all arguments should be tensors') + args1 = [x for x in args if x.is_Tensor] + args2 = [x for x in args if not x.is_Tensor] + t0 = args1[0] + if t0.is_TensAdd: + t0 = t0.args[0] + if t0.free: + raise ValueError('all tensors must have the same indices') + t1 = Tensor(Add(*args2), [], [], []) + args = [t1] + args1 a = [] for x in args: if x.is_TensAdd: @@ -438,9 +471,13 @@ def __new__(cls, *args, **kw_args): else: a.append(x) args = a - indices = set([x[0] for x in args[0].free]) - if not all(set([y[0] for y in x.free]) == indices for x in args[1:]): + + indices0 = sorted([x[0] for x in args[0].free], key=lambda x: x.name) + list_indices = [sorted([y[0] for y in x.free], key=lambda x: x.name) for x in args[1:]] + if not all(x == indices0 for x in list_indices): + print 'ERR list_indices=%s indices0=%s' %(list_indices, indices0) raise ValueError('all tensors must have the same indices') + obj = Basic.__new__(cls, **kw_args) args.sort(key=lambda x: (x.components, x.free, x.dum)) a = [] @@ -449,6 +486,10 @@ def __new__(cls, *args, **kw_args): prev_coeff = prev.coeff changed = False new = 0 + if len(args) == 1 and args[0].is_TensMul: + obj._args = tuple(args) + return obj + for x in args[1:]: # if x and prev have the same tensor, update the coeff of prev if x.components == prev.components \ @@ -484,18 +525,39 @@ def __new__(cls, *args, **kw_args): # TODO introduce option not to use canon_bp automatically in TensAdd if all(x.is_TensMul for x in a): - a = [x.canon_bp() for x in a] + a = [canon_bp(x) for x in a] obj._args = tuple(a) - obj.set_indices = indices return obj def canon_bp(self): args = [x.canon_bp() for x in self.args] args.sort(key=lambda x: (x.components, x.free, x.dum)) - return TensAdd(*args) + res = TensAdd(*args) + return res def __eq__(self, other): - return self - other == 0 + other = sympify(other) + if not other.is_Tensor: + if len(self.args) == 1: + return self.args[0].coeff == other + t = self - other + if not t.is_Tensor: + return t == 0 + else: + if t.is_TensMul: + return t.coeff == 0 + else: + return all(x.coeff == 0 for x in t.args) + + def __neq__(self, other): + return not (self == other) + + def contract_metric(self, g, contract_all=False): + args = [x.contract_metric(g, contract_all) for x in self.args] + if len(args) == 1: + return args[0] + t = TensAdd(*args) + return t.canon_bp() def _pretty(self): a = [] @@ -549,14 +611,21 @@ def __new__(cls, coeff, *args, **kw_args): def __eq__(self, other): if not other.is_Tensor : return False + res = self - other + return res == 0 + res = self.components == other.components and \ - self.free == other.free and \ - self.dum == other.dum and self.coeff == other.coeff + sorted(self.free) == sorted(other.free) and \ + sorted(self.dum) == sorted(other.dum) and self.coeff == other.coeff return res def __neq__(self, other): return not self == other + @property + def set_free_indices(self): + return set([x[0] for x in self.free]) + @staticmethod def from_indices(indices, types): """ @@ -877,6 +946,8 @@ def canon_bp(self): from sympy.combinatorics.tensor_can import canonicalize if self._is_canon_bp: return self + if not self.components: + return self t = self.sorted_components() g, dummies, msym, v = t.canon_args() can = canonicalize(g, dummies, msym, *v) @@ -885,7 +956,110 @@ def canon_bp(self): return t.perm2tensor(can, True) + def contract_metric(self, g, contract_all=False): + if not self.components: + return self + free_indices = [x[0] for x in self.free] + a = self.split() + typ = g.index_types[0] + # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim + for i, tx in enumerate(a): + if tx.components[0] == g: + free_indices_g = [x[0] for x in a[i].free] + if len(free_indices_g) == 0: + a1 = a[:i] + a[i + 1:] + t = tensor_mul(*a1)*(typ.dim*a[i].coeff) + if contract_all == True and g in t.components: + return t.contract_metric(g, True) + return t + + # if all metric tensors have only free indices, there is no contraction + for i, tg in enumerate(a): + if tg.components[0] == g: + tg_free_indices = [x[0] for x in tg.free] + if all(indx in free_indices for indx in tg_free_indices): + continue + break + else: + return self + + # tg has one or two indices contracted with other tensors + # i position of tg in a + coeff = S.One + tg_free = tg.free + if tg_free[0][0] in free_indices or tg_free[1][0] in free_indices: + if tg_free[0][0] in free_indices: + ind_free = tg_free[0][0] + ind, ipos1, _ = tg_free[1] + else: + ind_free = tg_free[1][0] + ind, ipos1, _ = tg_free[0] + + ind1 = -ind + # search ind1 in self.dum + for j, tx in enumerate(a): + if ind1 in [x[0] for x in tx.free]: + break + free1 = [] + for indx, iposx, _ in tx.free: + if indx == ind1: + free1.append((ind_free, iposx, 0)) + else: + free1.append((indx, iposx, 0)) + t1 = Tensor(tx.coeff, tx.components, free1, tx.dum) + a[j] = t1 + a = a[:i] + a[i + 1:] + coeff = coeff*tg.coeff + res = tensor_mul(*a) + else: + # tg has two indices contracted with other tensors + ind1 = tg_free[0][0] + ind2 = tg_free[1][0] + ind1m = -ind1 + ind2m = -ind2 + for k, ty in enumerate(a): + if ind2m in [x[0] for x in ty.free]: + break + if ty.components == [g]: + ty_indices = [x[0] for x in ty.free] + if all(x in [ind1m, ind2m] for x in ty_indices): + if i < k: + a = a[:i] + a[i+1:k] + a[k+1:] + else: + a = a[:k] + a[k+1:i] + a[k+1:] + if a: + res = tensor_mul(*a) + res = (coeff*typ.dim*tg.coeff*ty.coeff)*res + else: + res = coeff*typ.dim*tg.coeff*ty.coeff + res = Tensor(res, [],[],[], is_canon_bp=True) + if contract_all == True and g in res.components: + return res.contract_metric(g, True) + return res + + free2 = [] + for indx, iposx, _ in ty.free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + else: + free2.append((indx, iposx, 0)) + t2 = Tensor(ty.coeff, ty.components, free2, ty.dum) + a[k] = t2 + a = a[:i] + a[i + 1:] + coeff = coeff*tg.coeff + res = tensor_mul(*a) + res = coeff*res + if contract_all == True and g in res.components: + return res.contract_metric(g, True) + return res + + + + + def _pretty(self): + if self.components == []: + return str(self.coeff) indices = [str(ind) for ind in self.get_indices()] pos = 0 a = [] @@ -920,6 +1094,8 @@ def tensor_mul(*a): """ product of tensors """ + if not a: + return Tensor(S.One, [], [], []) t = a[0] for tx in a[1:]: t = t*tx diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 856ae0e020ee..c6cbfd9cbd4f 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -1,11 +1,10 @@ -from sympy.core import S, Rational +from sympy.core import S, Rational, Symbol from sympy.combinatorics import Permutation from sympy.combinatorics.tensor_can import (bsgs_direct_product, riemann_bsgs) from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replaceR, riemann_cyclic) - def test_get_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) @@ -520,3 +519,102 @@ def test_div(): assert t1._is_canon_bp t1 = t1/4 assert t1._is_canon_bp + +def test_metric_contract1(): + D = Symbol('D') + Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([Lorentz]*2, sym2) + g = Lorentz.metric + A, B = S2('A,B') + + # case with g with all free indices + t1 = A(a,b)*B(-b,c)*g(d, e) + t2 = t1.contract_metric(g) + assert t1 == t2 + + # case of g(d, -d) + t1 = A(a,b)*B(-b,c)*g(-d, d) + t2 = t1.contract_metric(g) + assert t2 == D*A(a, d)*B(-d, c) + + # g with one free index + t1 = A(a,b)*B(-b,-c)*g(c, d) + t2 = t1.contract_metric(g) + assert t2 == A(a, c)*B(-c, d) + + # g with both indices contracted with another tensor + t1 = A(a,b)*B(-b,-c)*g(c, -a) + t2 = t1.contract_metric(g) + assert t2 == A(a, b)*B(-b, -a) + + t1 = A(a,b)*B(-b,-c)*g(c, d)*g(-a, -d) + t2 = t1.contract_metric(g) + t2 = t2.contract_metric(g) + assert t2 == A(a,b)*B(-b,-a) + +def test_metric_contract1(): + D = Symbol('D') + Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) + g = Lorentz.metric + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym1) + p, q = S1('p,q') + + t1 = g(a,b)*p(c)*p(-c) + t2 = 3*g(-a,-b)*q(c)*q(-c) + t = t1*t2 + t = t.contract_metric(g) + t = t.contract_metric(g) + assert t == 3*D*p(a)*p(-a)*q(b)*q(-b) + t1 = g(a,b)*p(c)*p(-c) + t2 = 3*q(-a)*q(-b) + t = t1*t2 + t = t.contract_metric(g) + t = t.canon_bp() + assert t == 3*p(a)*p(-a)*q(b)*q(-b) + + t1 = 2*g(a,b)*p(c)*p(-c) + t2 = - 3*g(-a,-b)*q(c)*q(-c) + t = t1*t2 + t = t.contract_metric(g) + t = t.contract_metric(g) + t = 6*g(a,b)*g(-a,-b)*p(c)*p(-c)*q(d)*q(-d) + t = t.contract_metric(g) + t = t.contract_metric(g) + + t1 = 2*g(a,b)*p(c)*p(-c) + t2 = q(-a)*q(-b) + 3*g(-a,-b)*q(c)*q(-c) + t = t1*t2 + t = t.contract_metric(g) + assert t == (2 + 6*D)*p(a)*p(-a)*q(b)*q(-b) + + t1 = p(a)*p(b) + p(a)*q(b) + 2*g(a,b)*p(c)*p(-c) + t2 = q(-a)*q(-b) - g(-a,-b)*q(c)*q(-c) + t = t1*t2 + t = t.contract_metric(g) + t1 = (1 - 2*D)*p(a)*p(-a)*q(b)*q(-b) + p(a)*q(-a)*p(b)*q(-b) + assert t == t1 + + t = g(a,b)*g(c,d)*g(-b,-c) + t1 = t.contract_metric(g) + t1 = t1.canon_bp() + assert t1 == g(a,c)*g(d,-c) + + t2 = t1.contract_metric(g) + assert t2 == g(a, d) + + t1 = t.contract_metric(g, True) + assert t1 == g(a, d) + + t1 = g(a,b)*g(c,d) + g(a,c)*g(b,d) + g(a,d)*g(b,c) + t2 = t1.substitute_indices((a,-a),(b,-b),(c,-c),(d,-d)) + t = t1*t2 + t3 = t.contract_metric(g) + t3 = t3.contract_metric(g) + t3 = t3.contract_metric(g) + assert t3 == 3*D**2 + 6*D + t = t.contract_metric(g, True) + assert t == 3*D**2 + 6*D From 95000521023c2203abaef01db05fde106a900211 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 20 Dec 2012 12:58:09 +0100 Subject: [PATCH 08/47] gamma matrices in dimensional regularization --- sympy/tensor/dgamma_matr.py | 532 +++++++++++++++++++++++++ sympy/tensor/tests/test_dgamma_matr.py | 278 +++++++++++++ 2 files changed, 810 insertions(+) create mode 100644 sympy/tensor/dgamma_matr.py create mode 100644 sympy/tensor/tests/test_dgamma_matr.py diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py new file mode 100644 index 000000000000..6aeae6d74eb9 --- /dev/null +++ b/sympy/tensor/dgamma_matr.py @@ -0,0 +1,532 @@ +from sympy import Symbol, S, I +from sympy.combinatorics import Permutation +from sympy.tensor.tensor import (TensorIndexType, tensor_indices, + TensorSymmetry, get_symmetric_group_sgs, TensorType, tensor_mul, Tensor, + TensAdd, TensorHead, tensorlist_contract_metric) + + +class GammaMatrices(object): + """ + Gamma matrices in dimensional regularization + with G5 naively anticommuting with all gamma matrices (NDR) + + NDR is inconsistent but widely used. + Notice in particular that the cyclic property of the + trace in presence of G5 does not hold. + + The original dimensional regularization scheme by 't Hooft and Veltman + has `G5` anticommuting only with `G(m)` for `m` in 0,1,2,3, + and commuting for `m > 3`. It is consistent, and it is used to + compute the chiral anomaly; however it breaks gauge invariance + involving G5 even when there are no anomalies, which complicates + a lot maintaining the Ward (or Slavnov-Taylor) identities. + + TODO: introduce the option of using the 't Hooft and Veltman scheme. + """ + + def __init__(self, typ, gctr=4, g5c=I): + """ + typ TensorIndexType (Lorentz or Eulidean) + + gct3 `tr(G(m)*G(n)) = gctr*g(m,n)` + in D dimensions, when D is an integer, the gamma + matrices are represented with matrices or rank `2**(D//2)`, + so `gctr = 2**(D//2)`; + in dimensional regularization one uses usually + `gctr = 4*g(m,n)`, so we choose `gctr=4` as default. + + g5c `tr(G(m0)*G(m1)*G(m2)*G(m3)) = gctr*g5c*epsilon(m0,m1,m2,m3)` + with Lorentz signature `g5c = I`, which is the default; + in Euclidean space `g5c = 1` + + """ + self.g = typ.metric + if not typ.dim: + raise ValueError('Dimension not assigned') + self.D = typ.dim + self.typ = typ + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([typ], sym1) + self.G = S1('G', None) + sym0 = TensorSymmetry(([], [Permutation(1)])) + S0 = TensorType([], sym0) + self.Gamma5 = S0('G5', None) + self.G5 = Tensor(S.One, [self.Gamma5], [], []) + self.g5c = g5c + self.epsilon = typ.epsilon + self.eps_dim = typ.eps_dim + self.gctr = gctr + + def G5_to_right(self, t): + """ + move G5 to the right + """ + if not t.is_Tensor: + return t + if t.is_TensAdd: + a = [self.G5_to_right(x) for x in t.args] + return TensAdd(*a) + components = t.components + if not t.components: + return t + ncomps = len(components) + G = self.G + Gamma5 = self.Gamma5 + G5 = self.G5 + # [g5,g,g,g5,g,g] + numG = 0 + vposG5 = [] + for i in range(ncomps): + if components[i] == Gamma5: + vposG5.append(i) + for j in range(i + 1, ncomps): + if components[j] == G: + numG += 1 + ct = t.coeff if 0 in vposG5 else S.One + a = t.split() + a1 = [] + for i in range(ncomps): + if not i in vposG5: + a1.append(a[i]) + for i in range(ncomps - 1, -1, -1): + if components[i] == G: + break + if len(vposG5) % 2: + a1 = a1[:i + 1] + [G5] + a1[i + 1:ncomps] + else: + a1 = a1[:i + 1] + a1[i + 1:ncomps] + t1 = ((-1)**numG*ct)*tensor_mul(*a1) + return t1 + + def match1_gamma(self, t, n): + #t = t.sorted_components() + components = t.components + ncomps = len(components) + G = self.G + for i in range(ncomps): + if not components[i] == G: + continue + for j in range(i + 1, ncomps): + if components[j] == G and abs(j - i) == n + 1 and \ + (0, 0, i, j) in t.dum: + return i, j + + + def rule1_gamma(self, t, n, r=None): + """ + simplify products of gamma matrices `G(m)*G(m_1)...*G(m_n)*G(-m)` + + t Gamma matrix monomial + n apply rule for `G(m)*G(m_1)...*G(m_n)*G(-m)` + + Examples + ======== + + >>> from sympy import Symbol, S + >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ + TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + >>> from sympy.tensor.dgamma_matr import GammaMatrices + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> GM = GammaMatrices(Lorentz) + >>> G = GM.G + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> GM.gamma_trace(G(m0)*G(m1)*G(-m0)*G(m3)) + (-4*D + 8)*metric(m1, m3) + >>> t = G(m1)*G(m0)*G(-m1)*G(-m0) + >>> GM.rule1_gamma(t, 1) + (-D + 2)*G(L_0)*G(-L_0) + """ + if not r: + r = self.match1_gamma(t, n) + if not r: + return t + i, j = r + a = t.split() + za = zip(a, range(len(a))) + tc = t.coeff if i == 0 else S.One + a1 = [x for x, y in za if y not in r] + D = self.D + if n == 0: + # G(m)*G(-m) = d + t1 = tensor_mul(*a1) + t2 = Tensor(D*t1.coeff*tc, t1.components, t1.free, t1.dum) + return t2 + if n == 1: + # G(m)*G(n)*G(-m) = (2 - d)*G(n) + t1 = tensor_mul(*a1) + t2 = Tensor((2-D)*t1.coeff*tc, t1.components, t1.free, t1.dum) + return t2 + if n == 2: + # G(m)*G(n1)*G(n2)*G(-m) = (d-4)*G(n1)*G(n2) + 4*delta(n1, n2) + t1 = ((D-4)*tc)*tensor_mul(*a1) + a2 = a[:i] + a[j + 1:] + ind1 = a[r[0] + 1].free[0][0] + ind2 = a[r[1] - 1].free[0][0] + if a2: + a2 = tensorlist_contract_metric(a2, self.g(ind1, ind2)) + t2 = (4*tc)*tensor_mul(*a2) + else: + t2 = (4*tc)*self.g(ind1, ind2) + return t1 + t2 + if n == 3: + # G(m)*G(n1)*G(n2)*G(n3)*G(-m) = (4-d)*G(n1)*G(n2)*G(n3) - \ + # 2*G(n3)*G(n2)*G(n1) + t1 = ((4-D)*tc)*tensor_mul(*a1) + a2a = a[i + 1:j] + a2a.reverse() + a2 = a[:i] + a2a + a[j + 1:] + t2 = (-2*tc)*tensor_mul(*a2[:]) + return t1 + t2 + if n > 3: + # G(m0)*G(m_1)*...*G(m_k)*G(-m0) = + # 2*G(m_k)*G(m_1)*...*G(m_(k-1)) - + # G(m0)*G(m_1)*...*G(m_(k-1))G(-m0)*G(m_k)) + a1 = a[:i] + [a[j-1]] + a[i+1:j-1] + a[j + 1:] + t1 = (2*tc)*tensor_mul(*a1) + a2 = a[:j-1] + [a[j], a[j-1]] + a[j + 1:] + t2 = -tensor_mul(*a2) + return t1 + t2 + + + def do_rule1_gamma(self, t, nmax=4, doall=False): + """ + simplify products of gamma matrices `G(m)*G(m_1)...*G(m_n)*G(-m)` + + `t` Gamma matrix expression + + nmax maximum number for which `rule1\_gamma(t, n)` is applied + + doall if true apply `rule1\_gamma` till the expression does not change + + Examples + ======== + + >>> from sympy import Symbol, S + >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ + TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + >>> from sympy.tensor.dgamma_matr import GammaMatrices + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> GM = GammaMatrices(Lorentz) + >>> G = GM.G + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> t = G(m1)*G(m0)*G(-m1)*G(-m0) + >>> GM.do_rule1_gamma(t, doall=True) + D*(-D + 2) + """ + if not t.is_Tensor: + return t + if t.is_TensMul: + for n in range(nmax + 1): + #print 'DB10 t=%s n=%d' %(t, n) + r = self.match1_gamma(t, n) + if not r: + continue + t1 = self.rule1_gamma(t, n, r) + t1 = t1.contract_metric(self.g, contract_all=True) + if doall: + if t1 != t: + t1 = self.do_rule1_gamma(t1, nmax, doall) + return t1 + return t + if t.is_TensAdd: + a = [self.do_rule1_gamma(tx, nmax, doall) for tx in t.args] + t1 = TensAdd(*a) + return t1.contract_metric(self.g, contract_all=True) + + def match2_gamma(self, t, n): + components = t.components + ncomps = len(components) + G = self.G + # G(a)*G(b)*...*p(-a)*p(-b) + for i in range(ncomps - 1 - n): + if not components[i] == G: + continue + if not components[i + n + 1] == G: + continue + p_pos1 = p_pos2 = 0 + for dx in t.dum: + if dx[2] == i: + p_pos1 = dx[3] + if dx[2] == i + n + 1: + p_pos2 = dx[3] + if p_pos1 > 0 and p_pos2 > 0: + if components[p_pos1] == components[p_pos2] \ + and components[p_pos1].commuting == 0: + return i, p_pos1, p_pos2 + return None + + def rule2_gamma(self, t, n): + """ + simplify `G(m0)*p(-m0)*G(i_1)...*G(i_n)*G(m1)*p(-m1)` + + `t` Gamma matrix monomial + `n` as in `G(m0)*p(-m0)*G(i_1)...*G(i_n)*G(m1)*p(-m1)` + + Examples + ======== + + >>> from sympy import Symbol, S + >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ + TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + >>> from sympy.tensor.dgamma_matr import GammaMatrices + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> GM = GammaMatrices(Lorentz) + >>> G = GM.G + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> p = S1('p') + >>> ps = G(m0)*p(-m0) + >>> t = G(m0)*ps*ps*G(-m0) + >>> GM.rule2_gamma(t, 0) + G(L_0)*G(-L_0)*p(L_1)*p(-L_1) + """ + t = t.canon_bp() + r = self.match2_gamma(t, n) + if not r: + return t + i0, i1, i2 = r + if n == 0: + a = t.split() + ct = t.coeff if i0 == 0 else S.One + p = a[i1] + ind_p = p.free[0][0] + # G(m0)*G(m1)*p(-m0)*p(-m1) = p(m0)*p(-m0) + a1 = a[:i0] + a[i0 + 2:i1] + a[i1 + 1:i2] + a[i2 + 1:] + a2 = [p.substitute_indices((ind_p, -ind_p)), p] + a3 = a1 + a2 + t = tensor_mul(*a3)*ct + return t + elif n == 1: + # G(m0)*G(ind1)*G(m1)*p(-m0)*p(-m1) = + # 2*G(m0)*p(-m0)*p(ind1) - G(ind1)*p(m0)*p(-m0) + a = t.split() + ind1 = a[i0 + 1].free[0][0] + ct = t.coeff if i0 == 0 else S.One + p = a[i1] + ind_p = p.free[0][0] + a1 = a[:i0] + [a[i0 + 1]] + a[i0 + 3:i1] + a[i1 + 1:i2] + a[i2 + 1:] + a2 = [p.substitute_indices((ind_p, -ind_p)), p] + a3 = a1 + a2 + t1 = tensor_mul(*a3)*(-ct) + args = [t1] + a1 = a[:i0 + 1] + a[i0 + 3:i2] + a[i2 + 1:] + p = a[i2] + ind_p = p.free[0][0] + a2 = [p.substitute_indices((ind_p, ind1))] + a3 = a1 + a2 + t2 = tensor_mul(*a3)*2 + t3 = t1 + t2 + return t3 + else: + raise NotImplementedError + + def do_rule2_gamma(self, t, nmax=1): + """ + simplify `G(m0)*p(-m0)*G(m1)*p(-m1)` to `p(m)*p(-m)` + + `t` Gamma matrix expression + + Examples + ======== + + >>> from sympy import Symbol, S + >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ + TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + >>> from sympy.tensor.dgamma_matr import GammaMatrices + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> GM = GammaMatrices(Lorentz) + >>> G = GM.G + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> p = S1('p') + >>> ps = G(m0)*p(-m0) + >>> M = Symbol('M') + >>> t = G(m0)*(ps + M)*(ps - M)*G(-m0) + >>> GM.do_rule2_gamma(t) + (-M**2)*G(L_0)*G(-L_0) + G(L_0)*G(-L_0)*p(L_1)*p(-L_1) + """ + if not t.is_Tensor: + return t + if t.is_TensMul: + for n in range(nmax + 1): + t = self.rule2_gamma(t, n) + if t.is_TensAdd: + a = [self.do_rule2_gamma(tx) for tx in t.args] + t = TensAdd(*a) + return t + + + def gamma_trace(self, t): + """ + Trace of gamma matrices + + Examples + ======== + + >>> from sympy import Symbol, S + >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ + TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + >>> from sympy.tensor.dgamma_matr import GammaMatrices + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> GM = GammaMatrices( Lorentz) + >>> gamma_trace = GM.gamma_trace + >>> G = GM.G + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> gamma_trace(G(m0)*G(m1)*G(-m0)*G(m3)) + (-4*D + 8)*metric(m1, m3) + + >>> p, q = S1('p,q') + >>> t = G(m0)*G(m1)*G(m2)*G(m3)*p(-m1)*q(-m3) + >>> gamma_trace(t) + -4*metric(m0, m2)*p(L_0)*q(-L_0) + 4*p(m0)*q(m2) + 4*p(m2)*q(m0) + """ + if not t.is_Tensor: + return self.gctr*t + t = self.G5_to_right(t) + if t.is_TensAdd: + a = [self.gamma_trace(x) for x in t.args] + return TensAdd(*a) + components = t.components + ncomps = len(components) + G = self.G + G5 = self.G5 + Gamma5 = self.Gamma5 + g = self.g + if all(x != G for x in components): + if any(x == Gamma5 for x in components): + return Tensor(S.Zero, [], [], []) + return self.gctr*t + withG5 = False + t = t.canon_bp() + components = t.components + for i in range(ncomps): + if components[i] == G: + break + for j in range(i + 1, ncomps): + if not components[j] == G: + break + else: + j = ncomps + numG = j - i + if any(x == Gamma5 for x in components): + withG5 = True + if withG5: + if numG < 4 or numG % 2 != self.eps_dim % 2: + return Tensor(S.Zero, [], [], []) + if numG == 4: + a = t.split() + indices = [x.free[0][0] for x in a[i:j]] + ct = t.coeff if i == 0 else S.One + t1 = (self.gctr*ct*self.g5c)*self.epsilon(*indices) + a2 = a[:i] + a[j + 1:] + t2 = tensor_mul(*a2) + res = t1*t2 + res = res.canon_bp() + return res + if numG > 4: + prev = Tensor(S.Zero, [], [], []) + t2 = t + while True: + t2 = self.do_rule1_gamma(t2) + t2 = t2.contract_metric(g, contract_all=True) + t2 = self.do_rule2_gamma(t2) + if t2 == prev: + break + prev = t2 + if t == t2: + a = t.split() + args = [] + for k1 in range(i, j): + ind1 = a[k1].free[0][0] + for k2 in range(k1 + 1, j): + ind2 = a[k2].free[0][0] + sign = 1 if (k1 - k2) % 2 == 1 else -1 + a1 = a[:k1] + a[k1 + 1:k2] + a[k2 + 1:] + a1 = tensorlist_contract_metric(a1, g(ind1, ind2)) + ct = t.coeff if k1 == 0 else S.One + t1 = (sign*ct)*tensor_mul(*a1) + args.append(t1) + t2 = TensAdd(*args) + return self.gamma_trace(t2) + else: + return self.gamma_trace(t2) + else: + # without G5 + if numG % 2 == 1: + return Tensor(S.Zero, [], [], []) + if numG > 4: + prev = Tensor(S.Zero, [], [], []) + t2 = t + while True: + t2 = self.do_rule1_gamma(t2) + t2 = t2.contract_metric(g, contract_all=True) + t2 = self.do_rule2_gamma(t2) + if t2 == prev: + break + prev = t2 + if t == t2: + a = t.split() + # G are from i to j1 included; anticommute G(j1) through + ind1 = a[i].free[0][0] + ind2 = a[i + 1].free[0][0] + aa = a[:i] + a[i + 2:] + aa = tensorlist_contract_metric(aa, g(ind1, ind2)) + ct = t.coeff if i == 0 else S.One + t1 = ct*tensor_mul(*aa) + args = [t1] + sign = 1 + for k in range(i + 2, j): + sign = -sign + ind2 = a[k].free[0][0] + aa = a[:i] + a[i + 1:k] + a[k + 1:] + aa = tensorlist_contract_metric(aa, g(ind1, ind2)) + t2 = sign*tensor_mul(*aa) + args.append(t2) + t3 = TensAdd(*args) + return self.gamma_trace(t3) + return self.gamma_trace(t2) + + a = t.split() + t1 = self.gamma_trace1(*a[i:j]) + a2 = a[:i] + a[j:] + t2 = tensor_mul(*a2) + ct = t.coeff if i == 0 else S.One + t3 = ct*t1*t2 + if not t3: + return t3 + t3 = t3.contract_metric(g, contract_all=True) + return t3 + + + def gamma_trace1(self, *a): + if not a: + return self.gctr + n = len(a) + if n%2 == 1: + return Tensor(S.Zero, [], [], []) + if n == 2: + ind0 = a[0].free[0][0] + ind1 = a[1].free[0][0] + return self.gctr*self.g(ind0, ind1) + if n == 4: + ind0 = a[0].free[0][0] + ind1 = a[1].free[0][0] + ind2 = a[2].free[0][0] + ind3 = a[3].free[0][0] + return self.gctr*(self.g(ind0, ind1)*self.g(ind2, ind3) - \ + self.g(ind0, ind2)*self.g(ind1, ind3) + self.g(ind0, ind3)*self.g(ind1, ind2)) + else: + raise NotImplementedError diff --git a/sympy/tensor/tests/test_dgamma_matr.py b/sympy/tensor/tests/test_dgamma_matr.py new file mode 100644 index 000000000000..ae2282415a9b --- /dev/null +++ b/sympy/tensor/tests/test_dgamma_matr.py @@ -0,0 +1,278 @@ +from sympy import Symbol, S, I +from sympy.tensor.tensor import (TensorIndexType, tensor_indices, \ +TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) +from sympy.tensor.dgamma_matr import GammaMatrices +from sympy.utilities.pytest import skip, XFAIL + + +D = Symbol('D') +Lorentz = TensorIndexType('Lorentz', dim=D, eps_dim=4, dummy_fmt='L') +m0, m1, m2, m3, m4, m5 = tensor_indices('m0,m1,m2,m3,m4,m5', Lorentz) +n0, n1, n2, n3, n4, n5 = tensor_indices('n0,n1,n2,n3,n4,n5', Lorentz) +sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) +S1 = TensorType([Lorentz], sym1) + +GM = GammaMatrices( Lorentz) +match1_gamma = GM.match1_gamma +G = GM.G +g = GM.g +epsilon = GM.epsilon +rule1_gamma = GM.rule1_gamma +do_rule1_gamma = GM.do_rule1_gamma +match2_gamma = GM.match2_gamma +rule2_gamma = GM.rule2_gamma +do_rule2_gamma = GM.do_rule2_gamma +gamma_trace = GM.gamma_trace +gamma_trace1 = GM.gamma_trace1 +gctr = GM.gctr + + +def test_rule1_gamma(): + t = 2*G(m2)*G(m0)*G(m1)*G(-m0)*G(-m1) + r = match1_gamma(t, 1) + t = rule1_gamma(t, 1) + r = match1_gamma(t, 0) + t = rule1_gamma(t, 0) + assert t == (D*(-2*D + 4))*G(m2) + + t = 3*G(m2)*G(m0)*G(m1)*G(-m0) + t = rule1_gamma(t, 1) + + t = G(m2)*G(m0)*G(m1)*G(-m0)*G(-m2) + r = match1_gamma(t, 1) + assert r == (1, 3) + t = rule1_gamma(t, 1) + t = rule1_gamma(t, 1) + assert t == ((-D + 2)**2)*G(m1) + + t = G(m0)*G(m1)*G(m2)*G(m3)*G(-m1) + r = match1_gamma(t, 2) + assert r == (1, 4) + t = rule1_gamma(t, 2) + assert t == (D - 4)*G(m0)*G(m2)*G(m3) + 4*g(m2, m3)*G(m0) + + t = G(m0)*G(m1)*G(m2)*G(m3)*G(-m1)*G(-m0) + r = match1_gamma(t, 2) + t = rule1_gamma(t, 2) + + t = G(m0)*G(m1)*G(m2)*G(m3)*G(-m1)*G(-m0) + t = do_rule1_gamma(t) + t = do_rule1_gamma(t) + assert t == ((D - 4)**2)*G(m2)*G(m3) + (8*D - 16)*g(m2, m3) + + t = G(m2)*G(m0)*G(m1)*G(-m2)*G(-m0) + t = do_rule1_gamma(t) + t = do_rule1_gamma(t) + assert t == ((-D + 2)*(D - 4) + 4)*G(m1) + + t = G(m3)*G(m1)*G(m0)*G(m2)*G(-m3)*G(-m0)*G(-m2) + t = do_rule1_gamma(t) + t = do_rule1_gamma(t) + t = do_rule1_gamma(t) + assert t == (-4*D + (-D + 2)**2*(D - 4) + 8)*G(m1) + + M = Symbol('M') + + p = S1('p') + ps = p(m0)*G(-m0) + t0 = ps + M + t = G(m0)*(ps + M)*G(-m0) + t = do_rule1_gamma(t) + assert t == (2 - D)*ps + D*M + + t = G(m0)*(ps + M)*G(-m0)*(ps + M) + t = do_rule1_gamma(t) + + t = 2*G(m0)*G(m1)*G(m2)*G(m3)*G(-m0) + t = do_rule1_gamma(t) + assert t == (-2*D + 8)*G(m1)*G(m2)*G(m3) - 4*G(m3)*G(m2)*G(m1) + + t = G(m5)*G(m0)*G(m1)*G(m4)*G(m2)*G(-m4)*G(m3)*G(-m0) + t = do_rule1_gamma(t) + t = do_rule1_gamma(t) + assert t == ((-D + 2)*(-D + 4))*G(m5)*G(m1)*G(m2)*G(m3) + (2*D - 4)*G(m5)*G(m3)*G(m2)*G(m1) + + t = -G(m0)*G(m1)*G(m2)*G(m3)*G(-m0)*G(m4) + t = do_rule1_gamma(t) + assert t == (D - 4)*G(m1)*G(m2)*G(m3)*G(m4) + 2*G(m3)*G(m2)*G(m1)*G(m4) + + t = G(-m5)*G(m0)*G(m1)*G(m2)*G(m3)*G(m4)*G(-m0)*G(m5) + t = do_rule1_gamma(t, doall=True) + assert t == ((-D + 4)**2 + 4)*G(m1)*G(m2)*G(m3)*G(m4) + (4*D - 16)*G(m3)*G(m2)*G(m1)*G(m4) + (4*D - 16)*G(m4)*G(m1)*G(m2)*G(m3) + 4*G(m2)*G(m1)*G(m4)*G(m3) + 4*G(m3)*G(m4)*G(m1)*G(m2) + 4*G(m4)*G(m3)*G(m2)*G(m1) + + +def test_rule2_gamma(): + p = S1('p') + M = Symbol('M') + ps = p(m0)*G(-m0) + t = ps*ps + t = t.canon_bp() + t = rule2_gamma(t, 0) + assert t == p(m0)*p(-m0) + + t = 2*G(m1)*ps*ps*G(m2) + t = rule2_gamma(t, 0) + assert t == 2*G(m1)*G(m2)*p(m0)*p(-m0) + + t0 = ps + M + t = G(m0)*(ps + M)*G(-m0) + t = do_rule1_gamma(t) + assert t == (2 - D)*ps + D*M + + t = G(m0)*(ps + M)*G(-m0)*(ps + M) + t = do_rule1_gamma(t) + t = do_rule2_gamma(t) + assert t == (-D + 2)*p(m0)*p(-m0) + (D*M + M*(-D + 2))*G(m0)*p(-m0) + D*M**2 + + +def test_gamma_trace(): + a = [G(m0), G(m1)] + t1 = gamma_trace1(*a) + a = [G(m0), G(m1), G(m2), G(m3)] + t1 = gamma_trace1(*a) + + t = G(m0)*G(m1) + t1 = gamma_trace(t) + assert t1 == 4*g(m0, m1) + t = G(m0)*G(m1)*G(m2)*G(m3) + t1 = gamma_trace(t) + t2 = -4*g(m0, m2)*g(m1, m3) + 4*g(m0, m1)*g(m2, m3) + 4*g(m0, m3)*g(m1, m2) + st2 = str(t2) + assert t1 == t2 + + t = G(m0)*G(m1)*G(m2)*G(m3)*G(m4)*G(-m0) + t1 = gamma_trace(t) + assert t1 == (-4*D)*g(m1, m3)*g(m2, m4) + (4*D)*g(m1, m2)*g(m3, m4) + \ + (4*D)*g(m1, m4)*g(m2, m3) + + t = G(-m5)*G(m0)*G(m1)*G(m2)*G(m3)*G(m4)*G(-m0)*G(m5) + t1 = gamma_trace(t) + assert t1 == (32*D + 4*(-D + 4)**2 - 64)*(g(m1, m2)*g(m3, m4) - \ + g(m1, m3)*g(m2, m4) + g(m1, m4)*g(m2, m3)) + + t = G(m0)*G(m1)*G(-m0)*G(m3) + t1 = gamma_trace(t) + assert t1 == (-4*D + 8)*g(m1, m3) + + t = G(m0)*G(m1)*G(m2)*G(m3)*G(m4)*G(m5) + t1 = gamma_trace(t) + t2 = t1*g(-m0, -m5) + t2 = t2.contract_metric(g) + assert t2 == D*gamma_trace(G(m1)*G(m2)*G(m3)*G(m4)) + + p, q = S1('p,q') + ps = p(m0)*G(-m0) + qs = q(m0)*G(-m0) + t = ps*qs*ps*qs + t1 = gamma_trace(t) + assert t1 == 8*p(m0)*q(-m0)*p(m1)*q(-m1) - 4*p(m0)*p(-m0)*q(m1)*q(-m1) + +def test_gamma5(): + G5 = GM.G5 + epsilon = GM.epsilon + t1 = GM.G5_to_right(2*G5) + assert t1 == 2*G5 + assert gamma_trace(t1) == 0 + + t = G5*G(m0) + t1 = GM.G5_to_right(t) + assert t1 == - G(m0)*G5 + + t = G5*G(m0)*G5*G(m1) + t1 = GM.G5_to_right(t) + assert t1 == -G(m0)*G(m1) + t = G5*G(m0)*G5*G(m1)*G5 + t1 = GM.G5_to_right(t) + assert t1 == -G(m0)*G(m1)*G5 + + t = (1 + G5)*(1 - G5) + t1 = GM.G5_to_right(t) + assert t1 == 0 + + t = G5*G(m0) + t1 = gamma_trace(t) + assert t1 == 0 + + t = G(m0)*G5*G(m1) + t1 = gamma_trace(t) + assert t1 == 0 + + t= G5*G(m0)*G(m1)*G(m2)*G(m3) + t1 = gamma_trace(t) + assert t1 == 4*I*epsilon(m0, m1, m2, m3) + + t= G5*G(m0)*G(-m0)*G(m1)*G(m2)*G(m3) + t1 = gamma_trace(t) + assert t1 == 0 + + t= G5*G(m0)*G(-m0)*G(m1)*G(m2)*G(m3)*G(m4) + t1 = gamma_trace(t) + assert t1 == 4*D*I*epsilon(m1, m2, m3, m4) + + t= G5*G(m0)*G(m1)*G(-m0)*G(m2)*G(m3)*G(m4) + t1 = gamma_trace(t) + assert t1 == (I*(-4*D + 8))*epsilon(m1, m2, m3, m4) + + t = G(m0)*(1 - G5) + t1 = gamma_trace(t) + assert t1 == 0 + + t = G(m0)*(1 - G5)*G(m1)*G(m2)*G(m3) + t1 = gamma_trace(t) + assert t1 == 4*I*epsilon(m0, m1, m2, m3) + 4*(g(m0,m1)*g(m2,m3) - \ + g(m0,m2)*g(m1,m3) + g(m0,m3)*g(m1,m2)) + + t = G(m0)*(1 - G5)*G(m1)*(1 + G5)*G(m2)*(1 - G5)*G(m3) + t1 = gamma_trace(t) + assert t1 == 16*I*epsilon(m0, m1, m2, m3) + 16*(g(m0,m1)*g(m2,m3) - \ + g(m0,m2)*g(m1,m3) + g(m0,m3)*g(m1,m2)) + + M = Symbol('M') + p, q = S1('p,q') + ps = p(m0)*G(-m0) + qs = q(m0)*G(-m0) + p2 = p(m0)*p(-m0) + q2 = q(m0)*q(-m0) + pq = p(m0)*q(-m0) + t = (ps + M)*G5*(qs + M)*G5 + t1 = gamma_trace(t) + assert t1 == -4*p(m0)*q(-m0) + 4*M**2 + + t = G(m0)*(ps + M)*G(m2)*(ps + qs + M)*G5 + t1 = gamma_trace(t) + assert t1 == -4*I*epsilon(m0,m2,m1,m3)*p(-m1)*q(-m3) + + t = G(m0)*(ps + M)*G(m1)*(ps + qs)*G(m2)*(1 - G5) + t1 = gamma_trace(t) + assert t1 == 4*M*(-g(m1, m2)*p(m0) -g(m1, m2)*q(m0) + \ + I*epsilon(m0, m1, m2, m3)*p(-m3) + I*epsilon(m0, m1, m2, m3)*q(-m3) + \ + g(m0, m1)*p(m2) + g(m0, m1)*q(m2) + g(m0, m2)*p(m1) + g(m0, m2)*q(m1)) + + t = G(m0)*G(m1)*G(m2)*G(m3)*G(m4)*G(m5)*G5 + t1 = gamma_trace(t) + t2 = t1*g(-m0, -m2) + t2 = t2.contract_metric(g, contract_all=True) + assert t2 == (-4*I*D + 8*I)*epsilon(m1, m3, m4, m5) + +def test_trace1(): + M = Symbol('M') + p, q = S1('p,q') + ps = p(m0)*G(-m0) + qs = q(m0)*G(-m0) + p2 = p(m0)*p(-m0) + q2 = q(m0)*q(-m0) + pq = p(m0)*q(-m0) + + t = ps*qs*ps*qs*ps*qs + t1 = gamma_trace(t) + assert t1 == -12*p2*pq*q2 + 16*pq*pq*pq + + t = ps*qs*ps*qs*ps*qs*ps*qs + t1 = gamma_trace(t) + assert t1 == -32*pq*pq*p2*q2 + 32*pq*pq*pq*pq + 4*p2*p2*q2*q2 + +def test_epsilon(): + t = G(m0)*G(m1)*G(m2)*G(m3)*G(n0)*G(n1)*G(n2)*G(n3)*epsilon(-n0,-n1,-n2,-n3) + + t1 = gamma_trace(t) + assert t1 == 96*epsilon(m0, m1, m2, m3) From fd56731182ade3e82d5f5d073a72d81028bc2332 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 20 Dec 2012 12:59:34 +0100 Subject: [PATCH 09/47] Improved the documentation --- doc/src/modules/tensor/index.rst | 2 + sympy/tensor/tensor.py | 177 ++++++++++++++++++++++-------- sympy/tensor/tests/test_tensor.py | 45 ++++++++ 3 files changed, 179 insertions(+), 45 deletions(-) diff --git a/doc/src/modules/tensor/index.rst b/doc/src/modules/tensor/index.rst index 853f490aef7f..f64d3e80dba5 100644 --- a/doc/src/modules/tensor/index.rst +++ b/doc/src/modules/tensor/index.rst @@ -14,3 +14,5 @@ Contents indexed.rst index_methods.rst + tensor.rst + dgamma_matr.rst diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 9168865a1583..ee4e340525ca 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -14,16 +14,24 @@ from sympy import cacheit class TensorIndexType(object): - def __init__(self, name, metric_sym=0, dim=None, dummy_fmt=None): + def __init__(self, name, metric_sym=0, dim=None, eps_dim = None, + dummy_fmt=None): """ name name of the tensor type - metric_sym: + `metric\_sym`: 0 symmetric 1 antisymmetric None no symmetry - dummy_fmt name of the head of dummy indices; by default it is + dim dimension, it can be a symbol or a positive integer + + `eps\_dim` dimension of the epsilon tensor; it is by default + equal to dim, if the latter is an integer; else + it can be assigned (for use in naive dimensional + regularization) + + `dummy\_fmt` name of the head of dummy indices; by default it is the name of the tensor type Examples @@ -40,6 +48,14 @@ def __init__(self, name, metric_sym=0, dim=None, dummy_fmt=None): self.dummy_fmt = '%s_%%d' % dummy_fmt self.metric = self.get_metric() self.dim = dim + if eps_dim: + self.eps_dim = eps_dim + else: + self.eps_dim = dim + if isinstance(self.eps_dim, int): + self.epsilon = self.get_epsilon() + else: + self.epsilon = None def get_metric(self): if self.metric_sym is None: @@ -49,6 +65,11 @@ def get_metric(self): metric = S2('metric') return metric + def get_epsilon(self): + sym = TensorSymmetry(get_symmetric_group_sgs(self.eps_dim, 1)) + Sdim = TensorType([self]*self.eps_dim, sym) + epsilon = Sdim('Eps') + return epsilon def __str__(self): @@ -63,7 +84,7 @@ class TensorIndex(object): An index can be in contravariant or in covariant form; in the latter case it is represented prepending a `-` to the index name. - Dummy indices have the head given by `dummy_fmt` + Dummy indices have the head given by `dummy\_fmt` Examples @@ -104,7 +125,7 @@ def __eq__(self, other): self.tensortype == other.tensortype and \ self.is_contravariant == other.is_contravariant - def __neq__(self, other): + def __ne__(self, other): return not (self == other) @cacheit @@ -132,15 +153,13 @@ class TensorSymmetry(object): """ Symmetry of a tensor - bsgs tuple (base, sgs) BSBS of the symmetry of the tensor - - Examples - ======== + bsgs tuple (base, sgs) BSGS of the symmetry of the tensor Examples ======== Define a symmetric tensor + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) @@ -180,6 +199,7 @@ class TensorType(Basic): ======== Define a symmetric tensor + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) @@ -214,6 +234,7 @@ def __call__(self, s, commuting=0): Define symmetric tensors `V`, `W` and `G`, respectively commuting, anticommuting and with no commutation symmetry + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs, canon_bp >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) @@ -266,13 +287,16 @@ def __eq__(self, other): return False return self.name == other.name and self.index_types == other.index_types - def __neq__(self, other): - return not self == other + def __ne__(self, other): + return not (self == other) def commutes_with(self, other): """ Returns 0 (1) if self and other (anti)commute Returns None if self and other do not (anti)commute + + TODO: it should be possible to assign rules for commutations + between tensors, to be used here. """ if self.commuting == 0 or other.commuting == 0: return 0 @@ -324,6 +348,14 @@ class TensExpr(Basic): Tensor objects are formed by products of component tensors, and include a coefficient, which is a SymPy expression. + + + In the internal representation contracted indices are represented + by `(ipos1, ipos2, icomp1, icomp2)`, where `icomp1` is the position + of the component tensor with contravariant index, `ipos1` is the + slot which the index occupies in that component tensor. + + Contracted indices are therefore nameless in the internal representation. """ _op_priority = 11.0 @@ -339,7 +371,6 @@ def __neg__(self): def __abs__(self): raise NotImplementedError - #@call_highest_priority('__radd__') def __add__(self, other): other = sympify(other) if self.is_TensAdd: @@ -347,44 +378,35 @@ def __add__(self, other): return TensAdd(*args) return TensAdd(self, other) - #@call_highest_priority('__add__') def __radd__(self, other): return TensAdd(other, self) - #@call_highest_priority('__rsub__') def __sub__(self, other): other = sympify(other) return TensAdd(self, -other) - #@call_highest_priority('__sub__') def __rsub__(self, other): return TensAdd(other, -self) - #@call_highest_priority('__rmul__') def __mul__(self, other): - #return TensMul(self, other) if self.is_TensAdd: return TensAdd(*[x*other for x in self.args]) if other.is_TensAdd: return TensAdd(*[self*x for x in other.args]) return Tensor.__mul__(self, other) - #@call_highest_priority('__mul__') def __rmul__(self, other): if self.is_TensMul: coeff = other*self.coeff return Tensor(coeff, self.components, self.free, self.dum) return TensAdd(*[x*other for x in self.args]) - #@call_highest_priority('__rpow__') def __pow__(self, other): raise NotImplementedError - #@call_highest_priority('__pow__') def __rpow__(self, other): raise NotImplementedError - #@call_highest_priority('__rdiv__') def __div__(self, other): other = sympify(other) if other.is_Tensor: @@ -393,7 +415,6 @@ def __div__(self, other): return Tensor(coeff, self.components, self.free, self.dum, is_canon_bp=self._is_canon_bp) - #@call_highest_priority('__div__') def __rdiv__(self, other): raise NotImplementedError() @@ -402,12 +423,12 @@ def __rdiv__(self, other): def substitute_indices(self, *index_tuples): """ - Return a tensor with indices substituted according to `index_tuples` + Return a tensor with indices substituted according to `index\_tuples` Examples ======== - from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) @@ -446,6 +467,9 @@ def substitute_indices(self, *index_tuples): return TensAdd(*args1) class TensAdd(TensExpr): + """ + Sum of tensors + """ is_Tensor = True is_TensAdd = True @@ -455,7 +479,7 @@ def __new__(cls, *args, **kw_args): """ args = [sympify(x) for x in args if x] if not all(x.is_Tensor for x in args): - args1 = [x for x in args if x.is_Tensor] + args1 = [x for x in args if x.is_Tensor and x.coeff] args2 = [x for x in args if not x.is_Tensor] t0 = args1[0] if t0.is_TensAdd: @@ -471,6 +495,9 @@ def __new__(cls, *args, **kw_args): else: a.append(x) args = a + args = [x for x in args if x.coeff] + if not args: + return S.Zero indices0 = sorted([x[0] for x in args[0].free], key=lambda x: x.name) list_indices = [sorted([y[0] for y in x.free], key=lambda x: x.name) for x in args[1:]] @@ -526,6 +553,9 @@ def __new__(cls, *args, **kw_args): # TODO introduce option not to use canon_bp automatically in TensAdd if all(x.is_TensMul for x in a): a = [canon_bp(x) for x in a] + a = [x for x in a if x] + if not a: + return S.Zero obj._args = tuple(a) return obj @@ -540,6 +570,8 @@ def __eq__(self, other): if not other.is_Tensor: if len(self.args) == 1: return self.args[0].coeff == other + if other.is_Tensor and other.is_TensMul and other.coeff == 0: + return self == 0 t = self - other if not t.is_Tensor: return t == 0 @@ -549,10 +581,18 @@ def __eq__(self, other): else: return all(x.coeff == 0 for x in t.args) - def __neq__(self, other): + def __ne__(self, other): return not (self == other) def contract_metric(self, g, contract_all=False): + """ + Raise or lower indices with the metric `g` + + g metric + + `contract\_all` if True, eliminate all `g` which are contracted + """ + args = [x.contract_metric(g, contract_all) for x in self.args] if len(args) == 1: return args[0] @@ -592,9 +632,6 @@ def __new__(cls, coeff, *args, **kw_args): component tensor; the corresponding covariant index is in the `ipos2` slot position in the `icomp2`-th component tensor. """ - # composite tensor - # p_i*p_i - # t = Tensor(None, t1, t2) obj = Basic.__new__(cls) obj.components = args[0] obj.types = [] @@ -609,6 +646,9 @@ def __new__(cls, coeff, *args, **kw_args): return obj def __eq__(self, other): + if other == 0: + return self.coeff == 0 + other = sympify(other) if not other.is_Tensor : return False res = self - other @@ -619,7 +659,7 @@ def __eq__(self, other): sorted(self.dum) == sorted(other.dum) and self.coeff == other.coeff return res - def __neq__(self, other): + def __ne__(self, other): return not self == other @property @@ -712,7 +752,7 @@ def get_indices(self): def split(self): """ - Returns a list of tensors, whose proouct is `self` + Returns a list of tensors, whose product is `self` Dummy indices contracted among different tensor components become free indices with the same name as the one used to @@ -732,8 +772,6 @@ def split(self): A(a, L_0)*B(-L_0, c) >>> t.split() [A(a, L_0), B(-L_0, c)] - - """ indices = self.get_indices() pos = 0 @@ -750,7 +788,7 @@ def canon_args(self): """ Returns (g, dummies, msym, v), the entries of `canonicalize` - see `canonicalize` in tensor_can.py + see `canonicalize` in `tensor\_can.py` """ # to be called after sorted_components from sympy.combinatorics.permutations import _af_new @@ -806,7 +844,6 @@ def canon_args(self): return _af_new(g), dummies, msym, v def __mul__(self, other): - #if not isinstance(other, Tensor): other = sympify(other) if not other.is_Tensor: coeff = self.coeff*other @@ -881,7 +918,7 @@ def perm2tensor(self, g, canon_bp=False): g permutation corrisponding to the tensor in the representation used in canonicalization - canon_bp if canon_bp is True, then `g` is the permutation + `canon\_bp` if True, then `g` is the permutation corresponding to the canonical form of the tensor """ from bisect import bisect_right @@ -901,7 +938,7 @@ def perm2tensor(self, g, canon_bp=False): icomp = -1 for i in range(rank): if i in vpos: - icomp += 1 + icomp += vpos.count(i) pos0 = i ipos = i - pos0 gi = g[i] @@ -922,8 +959,6 @@ def perm2tensor(self, g, canon_bp=False): if g[-1] != len(g) - 1: coeff = -coeff res = Tensor(coeff, components, free, dum, is_canon_bp=canon_bp) - #if canon_bp: - # res._canon_bp = True return res def canon_bp(self): @@ -933,6 +968,7 @@ def canon_bp(self): Examples ======== + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) @@ -957,6 +993,31 @@ def canon_bp(self): def contract_metric(self, g, contract_all=False): + """ + Raise or lower indices with the metric `g` + + `g` metric + + `contract\_all` if True, eliminate all `g` which are contracted + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> g = Lorentz.metric + >>> p, q = S1('p,q') + >>> t = p(m0)*q(m1)*g(-m0, -m1); t + p(L_1)*q(L_0)*metric(-L_1, -L_0) + >>> t.contract_metric(g) + p(-L_0)*q(L_0) + """ + if g.index_types[0].metric_sym != 0: + # TODO case of antisymmetric metric + raise NotImplementedError if not self.components: return self free_indices = [x[0] for x in self.free] @@ -1054,9 +1115,6 @@ def contract_metric(self, g, contract_all=False): return res - - - def _pretty(self): if self.components == []: return str(self.coeff) @@ -1064,7 +1122,10 @@ def _pretty(self): pos = 0 a = [] for t in self.components: - a.append('%s(%s)' % (t.name, ', '.join(indices[pos:pos + t.rank]))) + if t.rank > 0: + a.append('%s(%s)' % (t.name, ', '.join(indices[pos:pos + t.rank]))) + else: + a.append('%s' % t.name) pos += t.rank res = '*'. join(a) if self.coeff == S.One: @@ -1101,9 +1162,33 @@ def tensor_mul(*a): t = t*tx return t +def tensorlist_contract_metric(a, tg): + """ + contract `tg` with a tensor in the list `a = t.split()` + """ + ind1, ind2 = [x[0] for x in tg.free] + mind1 = -ind1 + mind2 = -ind2 + for i in range(len(a)): + t1 = a[i] + for j in range(len(t1.free)): + indx, ipos, _ = t1.free[j] + if indx == mind1 or indx == mind2: + ind3 = ind2 if indx == mind1 else ind1 + free1 = t1.free[:] + free1[j] = (ind3, ipos, 0) + t2 = Tensor(t1.coeff, t1.components, free1, t1.dum) + a[i] = t2 + return a + a.append(tg) + return a + + def riemann_cyclic_replaceR(t_r): """ - R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q) + replace Riemann tensor with an equivalent expression + + `R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q)` """ free = sorted(t_r.free, key=lambda x: x[1]) @@ -1117,7 +1202,9 @@ def riemann_cyclic_replaceR(t_r): def riemann_cyclic(t2): """ replace each Riemann tensor with an equivalent expression - satisfying the cyclic identity + satisfying the cyclic identity. + + This trick is discussed in the reference guide to Cadabra. Examples ======== diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index c6cbfd9cbd4f..02b353f532a8 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -504,6 +504,10 @@ def test_riemann_cyclic(): t1 = riemann_cyclic(t) assert t1 == 0 + t = R(i,j,k,l)*R(-k,-l,m,n)*(R(-m,-n,-i,-j) + 2*R(-m,-j,-n,-i)) + t1 = riemann_cyclic(t) + assert t1 == 0 + def test_div(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m0,m1,m2,m3 = tensor_indices('m0,m1,m2,m3', Lorentz) @@ -618,3 +622,44 @@ def test_metric_contract1(): assert t3 == 3*D**2 + 6*D t = t.contract_metric(g, True) assert t == 3*D**2 + 6*D + +def test_epsilon(): + Lorentz = TensorIndexType('Lorentz', dim=4, dummy_fmt='L') + a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) + g = Lorentz.metric + epsilon = Lorentz.epsilon + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym1) + p, q, r, s = S1('p,q,r,s') + + t = epsilon(b,a,c,d) + t1 = t.canon_bp() + assert t1 == -epsilon(a,b,c,d) + + t = epsilon(c,b,d,a) + t1 = t.canon_bp() + assert t1 == epsilon(a,b,c,d) + + t = epsilon(c,a,d,b) + t1 = t.canon_bp() + assert t1 == -epsilon(a,b,c,d) + + t = epsilon(a,b,c,d)*p(-a)*q(-b) + t1 = t.canon_bp() + assert t1 == epsilon(c, d, a, b)*p(-a)*q(-b) + + t = epsilon(c,b,d,a)*p(-a)*q(-b) + t1 = t.canon_bp() + assert t1 == epsilon(c, d, a, b)*p(-a)*q(-b) + + t = epsilon(c,a,d,b)*p(-a)*q(-b) + t1 = t.canon_bp() + assert t1 == -epsilon(c, d, a, b)*p(-a)*q(-b) + + t = epsilon(c,a,d,b)*p(-a)*p(-b) + t1 = t.canon_bp() + assert t1 == 0 + + t = epsilon(c,a,d,b)*p(-a)*q(-b) + epsilon(a,b,c,d)*p(-b)*q(-a) + t1 = t.canon_bp() + assert t1 == -2*epsilon(c, d, a, b)*p(-a)*q(-b) From 28378ea7b2c5e67fef25f770a8a7b4acad397d62 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 20 Dec 2012 13:16:24 +0100 Subject: [PATCH 10/47] Tensor expressions with canonicalization It is implemented a tensor class with monoterm canonicalization and algebraic operations on tensors. There are two applications: check of identities involving the cyclic symmetry of the Riemann tensor ``` >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, riemann_cyclic, riemann_bsgs >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) >>> symr = TensorSymmetry(riemann_bsgs) >>> R4 = TensorType([Lorentz]*4, symr) >>> R = R4('R') >>> t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) >>> riemann_cyclic(t) 0 ``` gamma matrices in dimensional regularization ``` >>> from sympy import Symbol, S >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) >>> from sympy.tensor.dgamma_matr import GammaMatrices >>> D = Symbol('D') >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) >>> S1 = TensorType([Lorentz], sym1) >>> GM = GammaMatrices(Lorentz) >>> gamma_trace = GM.gamma_trace >>> G = GM.G >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) >>> gamma_trace(G(m0)*G(m1)*G(-m0)*G(m3)) (-4*D + 8)*metric(m1, m3) >>> p, q = S1('p,q') >>> t = G(m0)*G(m1)*G(m2)*G(m3)*p(-m1)*q(-m3) >>> gamma_trace(t) -4*metric(m0, m2)*p(L_0)*q(-L_0) + 4*p(m0)*q(m2) + 4*p(m2)*q(m0) ``` --- sympy/tensor/dgamma_matr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index 6aeae6d74eb9..435377d3dc16 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -381,7 +381,7 @@ def gamma_trace(self, t): >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) >>> S1 = TensorType([Lorentz], sym1) - >>> GM = GammaMatrices( Lorentz) + >>> GM = GammaMatrices(Lorentz) >>> gamma_trace = GM.gamma_trace >>> G = GM.G >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) From d48be8a2837199375e4aeda40d71a4f77e3d5279 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 20 Dec 2012 13:28:47 +0100 Subject: [PATCH 11/47] Fixed bug in the documentation. --- sympy/tensor/tensor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index ee4e340525ca..23a10f750e6c 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -1010,10 +1010,11 @@ def contract_metric(self, g, contract_all=False): >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric >>> p, q = S1('p,q') - >>> t = p(m0)*q(m1)*g(-m0, -m1); t - p(L_1)*q(L_0)*metric(-L_1, -L_0) - >>> t.contract_metric(g) - p(-L_0)*q(L_0) + >>> t = p(m0)*q(m1)*g(-m0, -m1) + >>> t.canon_bp() + metric(L_0, L_1)*p(-L_0)*q(-L_1) + >>> t.contract_metric(g).canon_bp() + p(L_0)*q(-L_0) """ if g.index_types[0].metric_sym != 0: # TODO case of antisymmetric metric From 0389c6d6c428ebc63a5358fd7ae07bb114dec8c7 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 22 Dec 2012 11:08:28 +0100 Subject: [PATCH 12/47] added classes in test_args; fixed bug in substitute_indices and in TensAdd.__new__; incresed coverage; cleaned up the documentation --- sympy/core/tests/test_args.py | 41 +++ sympy/tensor/dgamma_matr.py | 73 +++-- sympy/tensor/tensor.py | 487 ++++++++++++++++-------------- sympy/tensor/tests/test_tensor.py | 47 ++- 4 files changed, 388 insertions(+), 260 deletions(-) diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py index df8584991607..7e8eb6473b31 100644 --- a/sympy/core/tests/test_args.py +++ b/sympy/core/tests/test_args.py @@ -2447,6 +2447,47 @@ def test_sympy__tensor__indexed__IndexedBase(): assert _test_args(IndexedBase('A', 1)) assert _test_args(IndexedBase('A')[0, 1]) +def test_sympy__tensor__tensor__TensorType(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, TensorType + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + assert _test_args(TensorType([Lorentz], sym)) + +def test_sympy__tensor__tensor__TensorHead(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, TensorHead + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym) + assert _test_args(TensorHead('p', S1, 0)) + +@SKIP("abstract class") +def test_sympy__tensor__tensor__TensExpr(): + pass + +def test_sympy__tensor__tensor__TensAdd(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, tensor_indices, TensAdd + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b = tensor_indices('a,b', Lorentz) + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym) + p, q = S1('p,q') + t1 = p(a) + t2 = q(a) + assert _test_args(TensAdd(t1, t2)) + + +def test_sympy__tensor__tensor__Tensor(): + from sympy.core import S + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, tensor_indices, Tensor + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b = tensor_indices('a,b', Lorentz) + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym) + p = S1('p') + free, dum = Tensor.from_indices(a) + assert _test_args(Tensor(S.One, [p], free, dum)) + + @XFAIL def test_as_coeff_add(): diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index 435377d3dc16..c2507fe56d70 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -66,8 +66,8 @@ def G5_to_right(self, t): if t.is_TensAdd: a = [self.G5_to_right(x) for x in t.args] return TensAdd(*a) - components = t.components - if not t.components: + components = t._components + if not components: return t ncomps = len(components) G = self.G @@ -82,7 +82,7 @@ def G5_to_right(self, t): for j in range(i + 1, ncomps): if components[j] == G: numG += 1 - ct = t.coeff if 0 in vposG5 else S.One + ct = t._coeff if 0 in vposG5 else S.One a = t.split() a1 = [] for i in range(ncomps): @@ -100,7 +100,7 @@ def G5_to_right(self, t): def match1_gamma(self, t, n): #t = t.sorted_components() - components = t.components + components = t._components ncomps = len(components) G = self.G for i in range(ncomps): @@ -108,7 +108,7 @@ def match1_gamma(self, t, n): continue for j in range(i + 1, ncomps): if components[j] == G and abs(j - i) == n + 1 and \ - (0, 0, i, j) in t.dum: + (0, 0, i, j) in t._dum: return i, j @@ -146,25 +146,25 @@ def rule1_gamma(self, t, n, r=None): i, j = r a = t.split() za = zip(a, range(len(a))) - tc = t.coeff if i == 0 else S.One + tc = t._coeff if i == 0 else S.One a1 = [x for x, y in za if y not in r] D = self.D if n == 0: # G(m)*G(-m) = d t1 = tensor_mul(*a1) - t2 = Tensor(D*t1.coeff*tc, t1.components, t1.free, t1.dum) + t2 = Tensor(D*t1._coeff*tc, t1._components, t1._free, t1._dum) return t2 if n == 1: # G(m)*G(n)*G(-m) = (2 - d)*G(n) t1 = tensor_mul(*a1) - t2 = Tensor((2-D)*t1.coeff*tc, t1.components, t1.free, t1.dum) + t2 = Tensor((2-D)*t1._coeff*tc, t1._components, t1._free, t1._dum) return t2 if n == 2: # G(m)*G(n1)*G(n2)*G(-m) = (d-4)*G(n1)*G(n2) + 4*delta(n1, n2) t1 = ((D-4)*tc)*tensor_mul(*a1) a2 = a[:i] + a[j + 1:] - ind1 = a[r[0] + 1].free[0][0] - ind2 = a[r[1] - 1].free[0][0] + ind1 = a[r[0] + 1]._free[0][0] + ind2 = a[r[1] - 1]._free[0][0] if a2: a2 = tensorlist_contract_metric(a2, self.g(ind1, ind2)) t2 = (4*tc)*tensor_mul(*a2) @@ -223,7 +223,6 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): return t if t.is_TensMul: for n in range(nmax + 1): - #print 'DB10 t=%s n=%d' %(t, n) r = self.match1_gamma(t, n) if not r: continue @@ -240,7 +239,7 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): return t1.contract_metric(self.g, contract_all=True) def match2_gamma(self, t, n): - components = t.components + components = t._components ncomps = len(components) G = self.G # G(a)*G(b)*...*p(-a)*p(-b) @@ -250,7 +249,7 @@ def match2_gamma(self, t, n): if not components[i + n + 1] == G: continue p_pos1 = p_pos2 = 0 - for dx in t.dum: + for dx in t._dum: if dx[2] == i: p_pos1 = dx[3] if dx[2] == i + n + 1: @@ -295,9 +294,9 @@ def rule2_gamma(self, t, n): i0, i1, i2 = r if n == 0: a = t.split() - ct = t.coeff if i0 == 0 else S.One + ct = t._coeff if i0 == 0 else S.One p = a[i1] - ind_p = p.free[0][0] + ind_p = p._free[0][0] # G(m0)*G(m1)*p(-m0)*p(-m1) = p(m0)*p(-m0) a1 = a[:i0] + a[i0 + 2:i1] + a[i1 + 1:i2] + a[i2 + 1:] a2 = [p.substitute_indices((ind_p, -ind_p)), p] @@ -308,10 +307,10 @@ def rule2_gamma(self, t, n): # G(m0)*G(ind1)*G(m1)*p(-m0)*p(-m1) = # 2*G(m0)*p(-m0)*p(ind1) - G(ind1)*p(m0)*p(-m0) a = t.split() - ind1 = a[i0 + 1].free[0][0] - ct = t.coeff if i0 == 0 else S.One + ind1 = a[i0 + 1]._free[0][0] + ct = t._coeff if i0 == 0 else S.One p = a[i1] - ind_p = p.free[0][0] + ind_p = p._free[0][0] a1 = a[:i0] + [a[i0 + 1]] + a[i0 + 3:i1] + a[i1 + 1:i2] + a[i2 + 1:] a2 = [p.substitute_indices((ind_p, -ind_p)), p] a3 = a1 + a2 @@ -319,7 +318,7 @@ def rule2_gamma(self, t, n): args = [t1] a1 = a[:i0 + 1] + a[i0 + 3:i2] + a[i2 + 1:] p = a[i2] - ind_p = p.free[0][0] + ind_p = p._free[0][0] a2 = [p.substitute_indices((ind_p, ind1))] a3 = a1 + a2 t2 = tensor_mul(*a3)*2 @@ -399,7 +398,7 @@ def gamma_trace(self, t): if t.is_TensAdd: a = [self.gamma_trace(x) for x in t.args] return TensAdd(*a) - components = t.components + components = t._components ncomps = len(components) G = self.G G5 = self.G5 @@ -411,7 +410,7 @@ def gamma_trace(self, t): return self.gctr*t withG5 = False t = t.canon_bp() - components = t.components + components = t._components for i in range(ncomps): if components[i] == G: break @@ -428,8 +427,8 @@ def gamma_trace(self, t): return Tensor(S.Zero, [], [], []) if numG == 4: a = t.split() - indices = [x.free[0][0] for x in a[i:j]] - ct = t.coeff if i == 0 else S.One + indices = [x._free[0][0] for x in a[i:j]] + ct = t._coeff if i == 0 else S.One t1 = (self.gctr*ct*self.g5c)*self.epsilon(*indices) a2 = a[:i] + a[j + 1:] t2 = tensor_mul(*a2) @@ -450,13 +449,13 @@ def gamma_trace(self, t): a = t.split() args = [] for k1 in range(i, j): - ind1 = a[k1].free[0][0] + ind1 = a[k1]._free[0][0] for k2 in range(k1 + 1, j): - ind2 = a[k2].free[0][0] + ind2 = a[k2]._free[0][0] sign = 1 if (k1 - k2) % 2 == 1 else -1 a1 = a[:k1] + a[k1 + 1:k2] + a[k2 + 1:] a1 = tensorlist_contract_metric(a1, g(ind1, ind2)) - ct = t.coeff if k1 == 0 else S.One + ct = t._coeff if k1 == 0 else S.One t1 = (sign*ct)*tensor_mul(*a1) args.append(t1) t2 = TensAdd(*args) @@ -480,17 +479,17 @@ def gamma_trace(self, t): if t == t2: a = t.split() # G are from i to j1 included; anticommute G(j1) through - ind1 = a[i].free[0][0] - ind2 = a[i + 1].free[0][0] + ind1 = a[i]._free[0][0] + ind2 = a[i + 1]._free[0][0] aa = a[:i] + a[i + 2:] aa = tensorlist_contract_metric(aa, g(ind1, ind2)) - ct = t.coeff if i == 0 else S.One + ct = t._coeff if i == 0 else S.One t1 = ct*tensor_mul(*aa) args = [t1] sign = 1 for k in range(i + 2, j): sign = -sign - ind2 = a[k].free[0][0] + ind2 = a[k]._free[0][0] aa = a[:i] + a[i + 1:k] + a[k + 1:] aa = tensorlist_contract_metric(aa, g(ind1, ind2)) t2 = sign*tensor_mul(*aa) @@ -503,7 +502,7 @@ def gamma_trace(self, t): t1 = self.gamma_trace1(*a[i:j]) a2 = a[:i] + a[j:] t2 = tensor_mul(*a2) - ct = t.coeff if i == 0 else S.One + ct = t._coeff if i == 0 else S.One t3 = ct*t1*t2 if not t3: return t3 @@ -518,14 +517,14 @@ def gamma_trace1(self, *a): if n%2 == 1: return Tensor(S.Zero, [], [], []) if n == 2: - ind0 = a[0].free[0][0] - ind1 = a[1].free[0][0] + ind0 = a[0]._free[0][0] + ind1 = a[1]._free[0][0] return self.gctr*self.g(ind0, ind1) if n == 4: - ind0 = a[0].free[0][0] - ind1 = a[1].free[0][0] - ind2 = a[2].free[0][0] - ind3 = a[3].free[0][0] + ind0 = a[0]._free[0][0] + ind1 = a[1]._free[0][0] + ind2 = a[2]._free[0][0] + ind3 = a[3]._free[0][0] return self.gctr*(self.g(ind0, ind1)*self.g(ind2, ind3) - \ self.g(ind0, ind2)*self.g(ind1, ind3) + self.g(ind0, ind3)*self.g(ind1, ind2)) else: diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 23a10f750e6c..2af9ff27fda8 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -1,37 +1,32 @@ from collections import defaultdict -from sympy.combinatorics.perm_groups import PermutationGroup -from sympy.combinatorics.permutations import Permutation, _af_new, _af_invert -from sympy.core import Basic, Symbol, sympify, Add, Mul, S, Rational -from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, canonical_free, riemann_bsgs - -from functools import wraps - -from sympy.core import S, Symbol, sympify, Tuple, Integer, Basic -from sympy.core.decorators import call_highest_priority -from sympy.core.sympify import SympifyError -from sympy.matrices import ShapeError -from sympy.simplify import simplify -from sympy import cacheit +from sympy.core import Basic, sympify, Add, Mul, S +from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs class TensorIndexType(object): + """ + A TensorIndexType is characterized by its name, by metric_sym, + giving the symmetry of its metric. + + If a dimension ``dim`` is defined, it can be a symbol or an integer. + """ def __init__(self, name, metric_sym=0, dim=None, eps_dim = None, dummy_fmt=None): """ name name of the tensor type - `metric\_sym`: + ``metric_sym``: 0 symmetric 1 antisymmetric None no symmetry dim dimension, it can be a symbol or a positive integer - `eps\_dim` dimension of the epsilon tensor; it is by default + ``eps_dim`` dimension of the epsilon tensor; it is by default equal to dim, if the latter is an integer; else it can be assigned (for use in naive dimensional regularization) - `dummy\_fmt` name of the head of dummy indices; by default it is + ``dummy_fmt`` name of the head of dummy indices; by default it is the name of the tensor type Examples @@ -71,6 +66,8 @@ def get_epsilon(self): epsilon = Sdim('Eps') return epsilon + def __lt__(self, other): + return self.name < other.name def __str__(self): return self.name @@ -81,10 +78,13 @@ class TensorIndex(object): """ Tensor indices are contructed with the Einstein summation convention. + A TensorIndex is chacterized by their ``name``, ``tensortype`` + and ``is_contravariant``. + An index can be in contravariant or in covariant form; in the latter - case it is represented prepending a `-` to the index name. + case it is represented prepending a ``-`` to the index name. - Dummy indices have the head given by `dummy\_fmt` + Dummy indices have a name with head given by ``tensortype.dummy_fmt`` Examples @@ -113,13 +113,6 @@ def __str__(self): __repr__ = __str__ - @cacheit - def covariant(self): - if self.is_contravariant: - return TensorIndex(self.name, self.tensortype, False) - else: - return self - def __eq__(self, other): return self.name == other.name and \ self.tensortype == other.tensortype and \ @@ -128,7 +121,9 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) - @cacheit + def __lt__(self, other): + return (self.tensortype, self.name) < (other.tensortype, other.name) + def __neg__(self): t1 = TensorIndex(self.name, self.tensortype, (not self.is_contravariant)) @@ -136,7 +131,11 @@ def __neg__(self): def tensor_indices(s, typ): """ - Returns tensor indices given their name + Returns tensor indices given their names and the TensorIndexType ``typ`` + + ``s`` string of comma separated names of indices + + ``typ`` list of TensorIndexType of the indices Examples ======== @@ -171,29 +170,10 @@ def __init__(self, bsgs): self.base, self.generators = bsgs self.rank = self.generators[0].size -def _get_index_types(indices): - res = [] - for i in indices: - if isinstance(i, TensorIndex): - res.append(i.tensortype) - elif isinstance(i, TensorIndexType): - res.append(i) - else: - raise NotImplementedError - return res - -def has_tensor(p): - if p.is_Tensor: - return True - if p.is_Mul or p.is_Add: - for x in p.args: - if has_tensor(x): - return True - return False - class TensorType(Basic): """ + A TensorType object is characterised by its index types and its symmetry Examples ======== @@ -209,14 +189,14 @@ class TensorType(Basic): """ is_commutative = False - def __new__(cls, indices, symmetry, **kw_args): + def __new__(cls, index_types, symmetry, **kw_args): obj = Basic.__new__(cls, **kw_args) obj.index_types = [] - obj.index_types = _get_index_types(indices) + obj.index_types = index_types obj.types = list(set(obj.index_types)) obj.types.sort(key=lambda x: x.name) obj.symmetry = symmetry - assert symmetry.rank == len(indices) + 2 + assert symmetry.rank == len(index_types) + 2 return obj def __str__(self): @@ -232,8 +212,8 @@ def __call__(self, s, commuting=0): Examples ======== - Define symmetric tensors `V`, `W` and `G`, respectively commuting, - anticommuting and with no commutation symmetry + Define symmetric tensors ``V``, ``W`` and ``G``, respectively + commuting, anticommuting and with no commutation symmetry >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs, canon_bp >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -261,6 +241,15 @@ def __new__(cls, name, typ, commuting, **kw_args): """ tensor with given name, index types, symmetry, commutation rule + ``name`` name of the tensor + + ``typ`` list of TensorIndexType + + ``commuting`` commutation property + 0 commuting tensor + 1 anticommuting tensor + None no commutation rule + Examples ======== @@ -290,10 +279,14 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) + def __lt__(self, other): + return (self.name, self.index_types) < (other.name, other.index_types) + def commutes_with(self, other): """ - Returns 0 (1) if self and other (anti)commute - Returns None if self and other do not (anti)commute + Returns 0 (1) if self and other (anti)commute. + + Returns None if self and other do not (anti)commute. TODO: it should be possible to assign rules for commutations between tensors, to be used here. @@ -304,10 +297,6 @@ def commutes_with(self, other): return 1 return None - #if self.commuting == None or other.commuting == None: - # return None - #return self.commuting * other.commuting - def __str__(self): return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) @@ -315,7 +304,7 @@ def __str__(self): def __call__(self, *indices): """ - tensor with indices + Returns a tensor with indices. Examples ======== @@ -330,7 +319,7 @@ def __call__(self, *indices): """ assert self.rank == len(indices) components = [self] - free, dum = Tensor.from_indices(indices, self.types) + free, dum = Tensor.from_indices(*indices) free.sort(key=lambda x: x[0].name) dum.sort() return Tensor(S.One, components, free, dum) @@ -351,8 +340,8 @@ class TensExpr(Basic): In the internal representation contracted indices are represented - by `(ipos1, ipos2, icomp1, icomp2)`, where `icomp1` is the position - of the component tensor with contravariant index, `ipos1` is the + by ``(ipos1, ipos2, icomp1, icomp2)``, where ``icomp1`` is the position + of the component tensor with contravariant index, ``ipos1`` is the slot which the index occupies in that component tensor. Contracted indices are therefore nameless in the internal representation. @@ -397,8 +386,8 @@ def __mul__(self, other): def __rmul__(self, other): if self.is_TensMul: - coeff = other*self.coeff - return Tensor(coeff, self.components, self.free, self.dum) + coeff = other*self._coeff + return Tensor(coeff, self._components, self._free, self._dum) return TensAdd(*[x*other for x in self.args]) def __pow__(self, other): @@ -411,8 +400,8 @@ def __div__(self, other): other = sympify(other) if other.is_Tensor: raise ValueError('cannot divide by a tensor') - coeff = self.coeff/other - return Tensor(coeff, self.components, self.free, self.dum, is_canon_bp=self._is_canon_bp) + coeff = self._coeff/other + return Tensor(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) def __rdiv__(self, other): @@ -423,7 +412,7 @@ def __rdiv__(self, other): def substitute_indices(self, *index_tuples): """ - Return a tensor with indices substituted according to `index\_tuples` + Return a tensor with free indices substituted according to ``index_tuples`` Examples ======== @@ -441,23 +430,20 @@ def substitute_indices(self, *index_tuples): A(j, L_0)*B(-L_0, -k) """ if self.is_TensMul: - free = self.free + free = self._free free1 = [] - for i, v in index_tuples: - for j, ipos, cpos in free: + for j, ipos, cpos in free: + for i, v in index_tuples: if i.name == j.name and i.tensortype == j.tensortype: if i.is_contravariant == j.is_contravariant: free1.append((v, ipos, cpos)) else: - # replace i with an index with the same covariance of i - if j.is_contravariant: - ind = TensorIndex(v.name, v.tensortype) - free1.append((ind, ipos, cpos)) - else: - ind = TensorIndex(v.name, v.tensortype) - ind = -ind - free1.append((ind, ipos, cpos)) - return Tensor(self.coeff, self.components, free1, self.dum) + free1.append((-v, ipos, cpos)) + break + else: + free1.append((j, ipos, cpos)) + + return Tensor(self._coeff, self._components, free1, self._dum) if self.is_TensAdd: args = self.args args1 = [] @@ -469,6 +455,22 @@ def substitute_indices(self, *index_tuples): class TensAdd(TensExpr): """ Sum of tensors + + Sum of more than one tensor are put automatically in canonical form. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, tensor_indices, TensAdd + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> sym = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym) + >>> p, q = S1('p,q') + >>> t1 = p(a) + >>> t2 = q(a) + >>> t1 + t2 + p(a) + q(a) """ is_Tensor = True is_TensAdd = True @@ -479,12 +481,19 @@ def __new__(cls, *args, **kw_args): """ args = [sympify(x) for x in args if x] if not all(x.is_Tensor for x in args): - args1 = [x for x in args if x.is_Tensor and x.coeff] + args1 = [] + for x in args: + if x.is_Tensor: + if x.is_TensAdd: + args1.extend(list(x.args)) + else: + args1.append(x) + args1 = [x for x in args1 if x.is_Tensor and x._coeff] args2 = [x for x in args if not x.is_Tensor] t0 = args1[0] if t0.is_TensAdd: t0 = t0.args[0] - if t0.free: + if t0._free: raise ValueError('all tensors must have the same indices') t1 = Tensor(Add(*args2), [], [], []) args = [t1] + args1 @@ -495,22 +504,22 @@ def __new__(cls, *args, **kw_args): else: a.append(x) args = a - args = [x for x in args if x.coeff] + args = [x for x in args if x._coeff] if not args: return S.Zero - indices0 = sorted([x[0] for x in args[0].free], key=lambda x: x.name) - list_indices = [sorted([y[0] for y in x.free], key=lambda x: x.name) for x in args[1:]] + indices0 = sorted([x[0] for x in args[0]._free], key=lambda x: x.name) + list_indices = [sorted([y[0] for y in x._free], key=lambda x: x.name) for x in args[1:]] if not all(x == indices0 for x in list_indices): - print 'ERR list_indices=%s indices0=%s' %(list_indices, indices0) raise ValueError('all tensors must have the same indices') obj = Basic.__new__(cls, **kw_args) - args.sort(key=lambda x: (x.components, x.free, x.dum)) + # collect terms + args.sort(key=lambda x: (x._components, x._free, x._dum)) a = [] pprev = None prev = args[0] - prev_coeff = prev.coeff + prev_coeff = prev._coeff changed = False new = 0 if len(args) == 1 and args[0].is_TensMul: @@ -519,9 +528,9 @@ def __new__(cls, *args, **kw_args): for x in args[1:]: # if x and prev have the same tensor, update the coeff of prev - if x.components == prev.components \ - and x.free == prev.free and x.dum == prev.dum: - prev_coeff = prev_coeff + x.coeff + if x._components == prev._components \ + and x._free == prev._free and x._dum == prev._dum: + prev_coeff = prev_coeff + x._coeff changed = True op = 0 else: @@ -532,18 +541,18 @@ def __new__(cls, *args, **kw_args): else: # get a tensor from prev with coeff=prev_coeff and store it if prev_coeff: - t = Tensor(prev_coeff, prev.components, - prev.free, prev.dum) + t = Tensor(prev_coeff, prev._components, + prev._free, prev._dum) a.append(t) # move x to prev op = 1 pprev, prev = prev, x - pprev_coeff, prev_coeff = prev_coeff, x.coeff + pprev_coeff, prev_coeff = prev_coeff, x._coeff changed = False # if the case op=0 prev was not stored; store it now # in the case op=1 x was not stored; store it now (as prev) if op == 0 and prev_coeff: - prev = Tensor(prev_coeff, prev.components, prev.free, prev.dum) + prev = Tensor(prev_coeff, prev._components, prev._free, prev._dum) a.append(prev) elif op == 1: a.append(prev) @@ -560,8 +569,12 @@ def __new__(cls, *args, **kw_args): return obj def canon_bp(self): + """ + canonicalize using the Butler-Portugal algorithm for canonicalization + under monoterm symmetries. + """ args = [x.canon_bp() for x in self.args] - args.sort(key=lambda x: (x.components, x.free, x.dum)) + args.sort(key=lambda x: (x._components, x._free, x._dum)) res = TensAdd(*args) return res @@ -569,28 +582,28 @@ def __eq__(self, other): other = sympify(other) if not other.is_Tensor: if len(self.args) == 1: - return self.args[0].coeff == other - if other.is_Tensor and other.is_TensMul and other.coeff == 0: + return self.args[0]._coeff == other + if other.is_Tensor and other.is_TensMul and other._coeff == 0: return self == 0 t = self - other if not t.is_Tensor: return t == 0 else: if t.is_TensMul: - return t.coeff == 0 + return t._coeff == 0 else: - return all(x.coeff == 0 for x in t.args) + return all(x._coeff == 0 for x in t.args) def __ne__(self, other): return not (self == other) def contract_metric(self, g, contract_all=False): """ - Raise or lower indices with the metric `g` + Raise or lower indices with the metric ``g`` - g metric + ``g`` metric - `contract\_all` if True, eliminate all `g` which are contracted + ``contract_all`` if True, eliminate all ``g`` which are contracted """ args = [x.contract_metric(g, contract_all) for x in self.args] @@ -601,17 +614,18 @@ def contract_metric(self, g, contract_all=False): def _pretty(self): a = [] - #args = sorted(self.args) args = self.args for x in args: a.append(str(x)) - a.sort() s = ' + '.join(a) s = s.replace('+ -', '- ') return s class Tensor(TensExpr): + """ + Product of tensors + """ is_Tensor = True is_TensMul = True @@ -620,64 +634,62 @@ def __new__(cls, coeff, *args, **kw_args): coeff SymPy expression coefficient of the tensor. - args[0] list of TensorHead of the component tensors. + ``args[0]`` list of TensorHead of the component tensors. - args[1] list of (ind, ipos, icomp) - where `ind` is a free index, `ipos` is the slot position - of `ind` in the `icomp`-th component tensor. + ``args[1]`` list of ``(ind, ipos, icomp)`` + where ``ind`` is a free index, ``ipos`` is the slot position + of ``ind`` in the ``icomp``-th component tensor. - args[2] list of tuples representing dummy indices. + ``args[2]`` list of tuples representing dummy indices. (ipos1, ipos2, icomp1, icomp2) indicates that the contravariant - dummy index is the `ipos1` slot position in the `icomp1`-th + dummy index is the ``ipos1``-th slot position in the ``icomp1``-th component tensor; the corresponding covariant index is - in the `ipos2` slot position in the `icomp2`-th component tensor. + in the ``ipos2`` slot position in the ``icomp2``-th component tensor. """ obj = Basic.__new__(cls) - obj.components = args[0] + obj._components = args[0] obj.types = [] - for t in obj.components: + for t in obj._components: obj.types.extend(t.types) - obj.free = args[1] - obj.dum = args[2] - obj.rank = len(obj.free) + 2*len(obj.dum) - obj.coeff = coeff + obj._free = args[1] + obj._dum = args[2] + obj.rank = len(obj._free) + 2*len(obj._dum) + obj._coeff = coeff obj._is_canon_bp = kw_args.get('is_canon_bp', False) return obj def __eq__(self, other): if other == 0: - return self.coeff == 0 + return self._coeff == 0 other = sympify(other) if not other.is_Tensor : return False res = self - other return res == 0 - res = self.components == other.components and \ - sorted(self.free) == sorted(other.free) and \ - sorted(self.dum) == sorted(other.dum) and self.coeff == other.coeff - return res - def __ne__(self, other): return not self == other - @property - def set_free_indices(self): - return set([x[0] for x in self.free]) - @staticmethod - def from_indices(indices, types): + def from_indices(*indices): """ - Returns free, dum for single component tensor + Convert ``indices`` into ``free``, ``dum`` for single component tensor - free list of tuples (index, pos, 0), - where `pos` is the position of index in + ``free`` list of tuples ``(index, pos, 0)``, + where ``pos`` is the position of index in the list of indices formed by the component tensors - dum list of tuples (pos_contr, pos_cov, 0, 0) + ``dum`` list of tuples ``(pos_contr, pos_cov, 0, 0)`` + Examples + ======== + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, Tensor, get_symmetric_group_sgs + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> Tensor.from_indices(m0, m1, -m1, m3) + ([(m0, 0, 0), (m3, 3, 0)], [(1, 2, 0, 0)]) """ n = len(indices) if n == 1: @@ -725,20 +737,34 @@ def get_indices(self): The indices are listed in the order in which they appear in the component tensors. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> g = Lorentz.metric + >>> p, q = S1('p,q') + >>> t = p(m1)*g(m0,m2) + >>> t.get_indices() + [m1, m0, m2] """ indices = [None]*self.rank start = 0 pos = 0 vpos = [] - components = self.components - for t in self.components: + components = self._components + for t in self._components: vpos.append(pos) pos += t.rank cdt = defaultdict(int) - for indx, ipos, cpos in self.free: + for indx, ipos, cpos in self._free: start = vpos[cpos] indices[start + ipos] = indx - for ipos1, ipos2, cpos1, cpos2 in self.dum: + for ipos1, ipos2, cpos1, cpos2 in self._dum: start1 = vpos[cpos1] start2 = vpos[cpos2] typ1 = components[cpos1].index_types[ipos1] @@ -752,7 +778,7 @@ def get_indices(self): def split(self): """ - Returns a list of tensors, whose product is `self` + Returns a list of tensors, whose product is ``self`` Dummy indices contracted among different tensor components become free indices with the same name as the one used to @@ -775,20 +801,20 @@ def split(self): """ indices = self.get_indices() pos = 0 - components = self.components + components = self._components res = [] - for t in self.components: + for t in self._components: t1 = t(*indices[pos:pos + t.rank]) pos += t.rank res.append(t1) - res[0] = Tensor(self.coeff, res[0].components, res[0].free, res[0].dum, is_canon_bp=res[0]._is_canon_bp) + res[0] = Tensor(self._coeff, res[0]._components, res[0]._free, res[0]._dum, is_canon_bp=res[0]._is_canon_bp) return res def canon_args(self): """ - Returns (g, dummies, msym, v), the entries of `canonicalize` + Returns ``(g, dummies, msym, v)``, the entries of ``canonicalize`` - see `canonicalize` in `tensor\_can.py` + see ``canonicalize`` in ``tensor_can.py`` """ # to be called after sorted_components from sympy.combinatorics.permutations import _af_new @@ -798,21 +824,21 @@ def canon_args(self): g = [None]*n + [n, n+1] pos = 0 vpos = [] - components = self.components - for t in self.components: + components = self._components + for t in self._components: vpos.append(pos) pos += t.rank - for i, (indx, ipos, cpos) in enumerate(self.free): + for i, (indx, ipos, cpos) in enumerate(self._free): pos = vpos[cpos] + ipos g[pos] = i - pos = len(self.free) - j = len(self.free) + pos = len(self._free) + j = len(self._free) dummies = [] prev = None a = [] msym = [] - for ipos1, ipos2, cpos1, cpos2 in self.dum: + for ipos1, ipos2, cpos1, cpos2 in self._dum: pos1 = vpos[cpos1] + ipos1 pos2 = vpos[cpos2] + ipos2 g[pos1] = j @@ -844,26 +870,47 @@ def canon_args(self): return _af_new(g), dummies, msym, v def __mul__(self, other): + """ + Multiply two tensors using Einstein summation convention. + + If the two tensors have an index in common, one contravariant + and the other covariant, in their product the indices are summed + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> S1 = TensorType([Lorentz], sym1) + >>> g = Lorentz.metric + >>> p, q = S1('p,q') + >>> t1 = p(m0) + >>> t2 = q(-m0) + >>> t1*t2 + p(L_0)*q(-L_0) + """ other = sympify(other) if not other.is_Tensor: - coeff = self.coeff*other - return Tensor(coeff, self.components, self.free, self.dum, is_canon_bp=self._is_canon_bp) + coeff = self._coeff*other + return Tensor(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) if other.is_TensAdd: return TensAdd(*[self*x for x in other.args]) - components = self.components + other.components + components = self._components + other._components # find out which free indices of self and other are contracted - free_dict1 = dict([(i.name, (pos, cpos, i)) for i, pos, cpos in self.free]) - free_dict2 = dict([(i.name, (pos, cpos, i)) for i, pos, cpos in other.free]) + free_dict1 = dict([(i.name, (pos, cpos, i)) for i, pos, cpos in self._free]) + free_dict2 = dict([(i.name, (pos, cpos, i)) for i, pos, cpos in other._free]) free_names = set(free_dict1.keys()) & set(free_dict2.keys()) # find the new `free` and `dum` - nc1 = len(self.components) - dum2 = [(i1, i2, c1 + nc1, c2 + nc1) for i1, i2, c1, c2 in other.dum] - free1 = [(ind, i, c) for ind, i, c in self.free if ind.name not in free_names] - free2 = [(ind, i, c + nc1) for ind, i, c in other.free if ind.name not in free_names] + nc1 = len(self._components) + dum2 = [(i1, i2, c1 + nc1, c2 + nc1) for i1, i2, c1, c2 in other._dum] + free1 = [(ind, i, c) for ind, i, c in self._free if ind.name not in free_names] + free2 = [(ind, i, c + nc1) for ind, i, c in other._free if ind.name not in free_names] free = free1 + free2 - dum = self.dum + dum2 + dum = self._dum + dum2 for name in free_names: ipos1, cpos1, ind1 = free_dict1[name] ipos2, cpos2, ind2 = free_dict2[name] @@ -875,7 +922,7 @@ def __mul__(self, other): else: new_dummy = (ipos2, ipos1, cpos2, cpos1) dum.append(new_dummy) - coeff = self.coeff*other.coeff + coeff = self._coeff*other._coeff return Tensor(coeff, components, free, dum) @@ -883,7 +930,8 @@ def sorted_components(self): """ Returns a tensor with sorted components """ - cv = zip(self.components, range(len(self.components))) + from sympy.combinatorics.permutations import _af_invert + cv = zip(self._components, range(len(self._components))) sign = 1 n = len(cv) - 1 for i in range(n): @@ -900,35 +948,35 @@ def sorted_components(self): components = [x[0] for x in cv] perm_inv = [x[1] for x in cv] perm = _af_invert(perm_inv) - free = [(ind, i, perm[c]) for ind, i, c in self.free] - dum = [(i1, i2, perm[c1], perm[c2]) for i1, i2, c1, c2 in self.dum] + free = [(ind, i, perm[c]) for ind, i, c in self._free] + dum = [(i1, i2, perm[c1], perm[c2]) for i1, i2, c1, c2 in self._dum] free.sort(key = lambda x: (x[0].tensortype, x[0].name)) dum.sort(key = lambda x: components[x[2]].index_types[x[0]]) if sign == -1: - coeff = -self.coeff + coeff = -self._coeff else: - coeff = self.coeff + coeff = self._coeff t = Tensor(coeff, components, free, dum) return t def perm2tensor(self, g, canon_bp=False): """ - Returns the tensor corresponding to the permutation `g` + Returns the tensor corresponding to the permutation ``g`` - g permutation corrisponding to the tensor in the representation + ``g`` permutation corrisponding to the tensor in the representation used in canonicalization - `canon\_bp` if True, then `g` is the permutation + ``canon_bp`` if True, then ``g`` is the permutation corresponding to the canonical form of the tensor """ from bisect import bisect_right vpos = [] - components = self.components + components = self._components pos = 0 - for t in self.components: + for t in self._components: vpos.append(pos) pos += t.rank - free_indices = [x[0] for x in self.free] + free_indices = [x[0] for x in self._free] sorted_free = sorted(free_indices, key=lambda x:(x.tensortype, x.name)) nfree = len(sorted_free) rank = self.rank @@ -955,7 +1003,7 @@ def perm2tensor(self, g, canon_bp=False): dum[idum][0] = ipos dum[idum][2] = icomp dum = [tuple(x) for x in dum] - coeff = self.coeff + coeff = self._coeff if g[-1] != len(g) - 1: coeff = -coeff res = Tensor(coeff, components, free, dum, is_canon_bp=canon_bp) @@ -975,6 +1023,9 @@ def canon_bp(self): >>> sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) >>> S2 = TensorType([Lorentz]*2, sym2a) >>> A = S2('A') + >>> t = A(m0,-m1)*A(m1,-m0) + >>> t.canon_bp() + -A(L_0, L_1)*A(-L_0, -L_1) >>> t = A(m0,-m1)*A(m1,-m2)*A(m2,-m0) >>> t.canon_bp() 0 @@ -982,7 +1033,7 @@ def canon_bp(self): from sympy.combinatorics.tensor_can import canonicalize if self._is_canon_bp: return self - if not self.components: + if not self._components: return self t = self.sorted_components() g, dummies, msym, v = t.canon_args() @@ -994,11 +1045,11 @@ def canon_bp(self): def contract_metric(self, g, contract_all=False): """ - Raise or lower indices with the metric `g` + Raise or lower indices with the metric ``g`` - `g` metric + ``g`` metric - `contract\_all` if True, eliminate all `g` which are contracted + ``contract_all`` if True, eliminate all ``g`` which are contracted Examples ======== @@ -1019,26 +1070,26 @@ def contract_metric(self, g, contract_all=False): if g.index_types[0].metric_sym != 0: # TODO case of antisymmetric metric raise NotImplementedError - if not self.components: + if not self._components: return self - free_indices = [x[0] for x in self.free] + free_indices = [x[0] for x in self._free] a = self.split() typ = g.index_types[0] # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim for i, tx in enumerate(a): - if tx.components[0] == g: - free_indices_g = [x[0] for x in a[i].free] + if tx._components[0] == g: + free_indices_g = [x[0] for x in a[i]._free] if len(free_indices_g) == 0: a1 = a[:i] + a[i + 1:] - t = tensor_mul(*a1)*(typ.dim*a[i].coeff) - if contract_all == True and g in t.components: + t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) + if contract_all == True and g in t._components: return t.contract_metric(g, True) return t # if all metric tensors have only free indices, there is no contraction for i, tg in enumerate(a): - if tg.components[0] == g: - tg_free_indices = [x[0] for x in tg.free] + if tg._components[0] == g: + tg_free_indices = [x[0] for x in tg._free] if all(indx in free_indices for indx in tg_free_indices): continue break @@ -1048,7 +1099,7 @@ def contract_metric(self, g, contract_all=False): # tg has one or two indices contracted with other tensors # i position of tg in a coeff = S.One - tg_free = tg.free + tg_free = tg._free if tg_free[0][0] in free_indices or tg_free[1][0] in free_indices: if tg_free[0][0] in free_indices: ind_free = tg_free[0][0] @@ -1058,20 +1109,20 @@ def contract_metric(self, g, contract_all=False): ind, ipos1, _ = tg_free[0] ind1 = -ind - # search ind1 in self.dum + # search ind1 in self._dum for j, tx in enumerate(a): - if ind1 in [x[0] for x in tx.free]: + if ind1 in [x[0] for x in tx._free]: break free1 = [] - for indx, iposx, _ in tx.free: + for indx, iposx, _ in tx._free: if indx == ind1: free1.append((ind_free, iposx, 0)) else: free1.append((indx, iposx, 0)) - t1 = Tensor(tx.coeff, tx.components, free1, tx.dum) + t1 = Tensor(tx._coeff, tx._components, free1, tx._dum) a[j] = t1 a = a[:i] + a[i + 1:] - coeff = coeff*tg.coeff + coeff = coeff*tg._coeff res = tensor_mul(*a) else: # tg has two indices contracted with other tensors @@ -1080,10 +1131,10 @@ def contract_metric(self, g, contract_all=False): ind1m = -ind1 ind2m = -ind2 for k, ty in enumerate(a): - if ind2m in [x[0] for x in ty.free]: + if ind2m in [x[0] for x in ty._free]: break - if ty.components == [g]: - ty_indices = [x[0] for x in ty.free] + if ty._components == [g]: + ty_indices = [x[0] for x in ty._free] if all(x in [ind1m, ind2m] for x in ty_indices): if i < k: a = a[:i] + a[i+1:k] + a[k+1:] @@ -1091,52 +1142,52 @@ def contract_metric(self, g, contract_all=False): a = a[:k] + a[k+1:i] + a[k+1:] if a: res = tensor_mul(*a) - res = (coeff*typ.dim*tg.coeff*ty.coeff)*res + res = (coeff*typ.dim*tg._coeff*ty._coeff)*res else: - res = coeff*typ.dim*tg.coeff*ty.coeff + res = coeff*typ.dim*tg._coeff*ty._coeff res = Tensor(res, [],[],[], is_canon_bp=True) - if contract_all == True and g in res.components: + if contract_all == True and g in res._components: return res.contract_metric(g, True) return res free2 = [] - for indx, iposx, _ in ty.free: + for indx, iposx, _ in ty._free: if indx == ind2m: free2.append((ind1, iposx, 0)) else: free2.append((indx, iposx, 0)) - t2 = Tensor(ty.coeff, ty.components, free2, ty.dum) + t2 = Tensor(ty._coeff, ty._components, free2, ty._dum) a[k] = t2 a = a[:i] + a[i + 1:] - coeff = coeff*tg.coeff + coeff = coeff*tg._coeff res = tensor_mul(*a) res = coeff*res - if contract_all == True and g in res.components: + if contract_all == True and g in res._components: return res.contract_metric(g, True) return res def _pretty(self): - if self.components == []: - return str(self.coeff) + if self._components == []: + return str(self._coeff) indices = [str(ind) for ind in self.get_indices()] pos = 0 a = [] - for t in self.components: + for t in self._components: if t.rank > 0: a.append('%s(%s)' % (t.name, ', '.join(indices[pos:pos + t.rank]))) else: a.append('%s' % t.name) pos += t.rank res = '*'. join(a) - if self.coeff == S.One: + if self._coeff == S.One: return res - elif self.coeff == -S.One: + elif self._coeff == -S.One: return '-%s' % res - if self.coeff.is_Atom: - return '%s*%s' % (self.coeff, res) + if self._coeff.is_Atom: + return '%s*%s' % (self._coeff, res) else: - return '(%s)*%s' %(self.coeff, res) + return '(%s)*%s' %(self._coeff, res) @@ -1146,10 +1197,6 @@ def canon_bp(p): """ if p.is_Tensor: return p.canon_bp() - if p.is_Add: - return Add(*[canon_bp(x) for x in p.args]) - if p.is_Mul: - return Mul(*[canon_bp(x) for x in p.args]) return p def tensor_mul(*a): @@ -1167,32 +1214,32 @@ def tensorlist_contract_metric(a, tg): """ contract `tg` with a tensor in the list `a = t.split()` """ - ind1, ind2 = [x[0] for x in tg.free] + ind1, ind2 = [x[0] for x in tg._free] mind1 = -ind1 mind2 = -ind2 for i in range(len(a)): t1 = a[i] - for j in range(len(t1.free)): - indx, ipos, _ = t1.free[j] + for j in range(len(t1._free)): + indx, ipos, _ = t1._free[j] if indx == mind1 or indx == mind2: ind3 = ind2 if indx == mind1 else ind1 - free1 = t1.free[:] + free1 = t1._free[:] free1[j] = (ind3, ipos, 0) - t2 = Tensor(t1.coeff, t1.components, free1, t1.dum) + t2 = Tensor(t1._coeff, t1._components, free1, t1._dum) a[i] = t2 return a a.append(tg) return a -def riemann_cyclic_replaceR(t_r): +def riemann_cyclic_replace(t_r): """ replace Riemann tensor with an equivalent expression - `R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q)` + ``R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q)`` """ - free = sorted(t_r.free, key=lambda x: x[1]) + free = sorted(t_r._free, key=lambda x: x[1]) m, n, p, q = [x[0] for x in free] t0 = S(2)/3*t_r t1 = - S(1)/3*t_r.substitute_indices((m,m),(n,q),(p,n),(q,p)) @@ -1222,7 +1269,7 @@ def riemann_cyclic(t2): """ a = t2.args a1 = [x.split() for x in a] - a2 = [[riemann_cyclic_replaceR(tx) for tx in y] for y in a1] + a2 = [[riemann_cyclic_replace(tx) for tx in y] for y in a1] a3 = [tensor_mul(*v) for v in a2] t3 = TensAdd(*a3) if not t3: diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 02b353f532a8..ef03eee0a6a9 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -3,14 +3,17 @@ from sympy.combinatorics.tensor_can import (bsgs_direct_product, riemann_bsgs) from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, - tensor_mul, canon_bp, TensAdd, riemann_cyclic_replaceR, riemann_cyclic) + tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, + tensorlist_contract_metric, Tensor) def test_get_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + assert a != -a sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) S2 = TensorType([Lorentz]*2, sym2) A, B = S2('A,B') + assert A != B t = A(a,b)*B(-b,c) indices = t.get_indices() L_0 = TensorIndex('L_0', Lorentz) @@ -18,6 +21,7 @@ def test_get_indices(): a = t.split() t2 = tensor_mul(*a) assert t == t2 + assert tensor_mul(*[]) == Tensor(S.One, [],[],[]) def test_canonicalize_no_slot_sym(): # A_d0 * B^d0; T_c = A^d0*B_d0 @@ -478,14 +482,30 @@ def test_substitute_indices(): t1a = A(j, l)*B(-l, -k) assert t1 == t1a -def test_riemann_cyclic_replaceR(): + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym1) + p = S1('p') + t = p(i) + t1 = t.substitute_indices((j, k)) + assert t1 == t + t1 = t.substitute_indices((i, j)) + assert t1 == p(j) + t1 = t.substitute_indices((i, -j)) + assert t1 == p(-j) + t1 = t.substitute_indices((-i, j)) + assert t1 == p(-j) + t1 = t.substitute_indices((-i, -j)) + assert t1 == p(j) + + +def test_riemann_cyclic_replace(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m0,m1,m2,m3 = tensor_indices('m0,m1,m2,m3', Lorentz) symr = TensorSymmetry(riemann_bsgs) R4 = TensorType([Lorentz]*4, symr) R = R4('R') t = R(m0,m2,m1,m3) - t1 = riemann_cyclic_replaceR(t) + t1 = riemann_cyclic_replace(t) t1a = -S.One/3*R(m0, m3, m2, m1) + S.One/3*R(m0, m1, m2, m3) + Rational(2,3)*R(m0, m2, m1, m3) assert t1 == t1a @@ -623,6 +643,27 @@ def test_metric_contract1(): t = t.contract_metric(g, True) assert t == 3*D**2 + 6*D + t = 2*p(a)*g(b,-b) + t1 = t.contract_metric(g) + assert t1 == 2*D*p(a) + + t = 2*p(a)*g(b,-a) + #t = 2*p(a)*g(-a, b) + t1 = t.contract_metric(g) + assert t1 == 2*p(b) + + M = Symbol('M') + t = (p(a)*p(b) + g(a, b)*M**2)*g(-a, -b) - D*M**2 + t1 = t.contract_metric(g) + assert t1 == p(a)*p(-a) + + v = [p(a), q(b), p(c)] + v1 = tensorlist_contract_metric(v, g(-a, -b)) + assert v1 == [p(-b), q(b), p(c)] + v = [p(a), q(b), p(c)] + v1 = tensorlist_contract_metric(v, g(d, e)) + assert v1 == [p(a), q(b), p(c), g(d, e)] + def test_epsilon(): Lorentz = TensorIndexType('Lorentz', dim=4, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) From a8a43ebc73e1bcf517051be69bd7c4424f91e18c Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 22 Dec 2012 18:58:50 +0100 Subject: [PATCH 13/47] Added the Kronecker delta. --- sympy/tensor/tensor.py | 118 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 2af9ff27fda8..ae9f010c3029 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -43,6 +43,7 @@ def __init__(self, name, metric_sym=0, dim=None, eps_dim = None, self.dummy_fmt = '%s_%%d' % dummy_fmt self.metric = self.get_metric() self.dim = dim + self.delta = self.get_kronecker_delta() if eps_dim: self.eps_dim = eps_dim else: @@ -60,6 +61,12 @@ def get_metric(self): metric = S2('metric') return metric + def get_kronecker_delta(self): + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([self]*2, sym2) + delta = S2('KD') + return delta + def get_epsilon(self): sym = TensorSymmetry(get_symmetric_group_sgs(self.eps_dim, 1)) Sdim = TensorType([self]*self.eps_dim, sym) @@ -558,7 +565,6 @@ def __new__(cls, *args, **kw_args): a.append(prev) if not a: return S.Zero - # TODO introduce option not to use canon_bp automatically in TensAdd if all(x.is_TensMul for x in a): a = [canon_bp(x) for x in a] @@ -597,6 +603,14 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) + def contract_delta(self, delta): + args = [x.contract_delta(delta) for x in self.args] + if len(args) == 1: + return args[0] + t = TensAdd(*args) + return t.canon_bp() + + def contract_metric(self, g, contract_all=False): """ Raise or lower indices with the metric ``g`` @@ -1043,6 +1057,106 @@ def canon_bp(self): return t.perm2tensor(can, True) + def contract_delta(self, delta): + if not self._components: + return self + free_indices = [x[0] for x in self._free] + a = self.split() + typ = delta.index_types[0] + # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim + for i, tx in enumerate(a): + if tx._components[0] == delta: + free_indices_g = [x[0] for x in a[i]._free] + if len(free_indices_g) == 0: + a1 = a[:i] + a[i + 1:] + t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) + if delta in t._components: + return t.contract_delta(delta, True) + return t + + # if all metric tensors have only free indices, there is no contraction + for i, tg in enumerate(a): + if tg._components[0] == delta: + tg_free_indices = [x[0] for x in tg._free] + if tg_free_indices[0].is_contravariant == tg_free_indices[1].is_contravariant: + raise ValueError('both indices are (contra)variant') + if all(indx in free_indices for indx in tg_free_indices): + continue + break + else: + return self + + # tg has one or two indices contracted with other tensors + # i position of tg in a + coeff = S.One + tg_free = tg._free + if tg_free[0][0] in free_indices or tg_free[1][0] in free_indices: + if tg_free[0][0] in free_indices: + ind_free = tg_free[0][0] + ind, ipos1, _ = tg_free[1] + else: + ind_free = tg_free[1][0] + ind, ipos1, _ = tg_free[0] + + ind1 = -ind + # search ind1 in self._dum + for j, tx in enumerate(a): + if ind1 in [x[0] for x in tx._free]: + break + free1 = [] + for indx, iposx, _ in tx._free: + if indx == ind1: + free1.append((ind_free, iposx, 0)) + else: + free1.append((indx, iposx, 0)) + t1 = Tensor(tx._coeff, tx._components, free1, tx._dum) + a[j] = t1 + a = a[:i] + a[i + 1:] + coeff = coeff*tg._coeff + res = tensor_mul(*a) + else: + # tg has two indices contracted with other tensors + ind1 = tg_free[0][0] + ind2 = tg_free[1][0] + ind1m = -ind1 + ind2m = -ind2 + for k, ty in enumerate(a): + if ind2m in [x[0] for x in ty._free]: + break + if ty._components == [delta]: + ty_indices = [x[0] for x in ty._free] + if all(x in [ind1m, ind2m] for x in ty_indices): + if i < k: + a = a[:i] + a[i+1:k] + a[k+1:] + else: + a = a[:k] + a[k+1:i] + a[k+1:] + if a: + res = tensor_mul(*a) + res = (coeff*typ.dim*tg._coeff*ty._coeff)*res + else: + res = coeff*typ.dim*tg._coeff*ty._coeff + res = Tensor(res, [],[],[], is_canon_bp=True) + if delta in res._components: + return res.contract_delta(delta) + return res + + free2 = [] + for indx, iposx, _ in ty._free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + else: + free2.append((indx, iposx, 0)) + t2 = Tensor(ty._coeff, ty._components, free2, ty._dum) + a[k] = t2 + a = a[:i] + a[i + 1:] + coeff = coeff*tg._coeff + res = tensor_mul(*a) + res = coeff*res + if delta in res._components: + return res.contract_delta(delta) + return res + + def contract_metric(self, g, contract_all=False): """ Raise or lower indices with the metric ``g`` @@ -1067,6 +1181,8 @@ def contract_metric(self, g, contract_all=False): >>> t.contract_metric(g).canon_bp() p(L_0)*q(-L_0) """ + delta = g.index_types[0].delta + self = self.contract_delta(delta) if g.index_types[0].metric_sym != 0: # TODO case of antisymmetric metric raise NotImplementedError From 552505f181ba91ff9e396d0386f4424c57294dce Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sun, 23 Dec 2012 18:16:28 +0100 Subject: [PATCH 14/47] Fixed bug in contract_delta --- sympy/tensor/tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index ae9f010c3029..e262219f09c6 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -1071,7 +1071,7 @@ def contract_delta(self, delta): a1 = a[:i] + a[i + 1:] t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) if delta in t._components: - return t.contract_delta(delta, True) + return t.contract_delta(delta) return t # if all metric tensors have only free indices, there is no contraction From 7f635fc74284ddf13cc5e1535d348225cc54a912 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 25 Dec 2012 10:17:36 +0100 Subject: [PATCH 15/47] Updated to PR 1700 --- sympy/core/tests/test_args.py | 27 +- sympy/printing/str.py | 5 +- sympy/tensor/dgamma_matr.py | 32 +- sympy/tensor/tensor.py | 396 +++++++++++++------------ sympy/tensor/tests/test_dgamma_matr.py | 2 +- sympy/tensor/tests/test_tensor.py | 76 +++-- 6 files changed, 308 insertions(+), 230 deletions(-) diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py index 7e8eb6473b31..5b3d3529d11f 100644 --- a/sympy/core/tests/test_args.py +++ b/sympy/core/tests/test_args.py @@ -2447,12 +2447,25 @@ def test_sympy__tensor__indexed__IndexedBase(): assert _test_args(IndexedBase('A', 1)) assert _test_args(IndexedBase('A')[0, 1]) +def test_sympy__tensor__tensor__TensorIndexType(): + from sympy.tensor.tensor import TensorIndexType + from sympy import Symbol + assert _test_args(TensorIndexType(Symbol('Lorentz'), metric_antisym=S.Zero)) + +@XFAIL +def test_sympy__tensor__tensor__TensorSymmetry(): + from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + assert _test_args(TensorSymmetry(get_symmetric_group_sgs(2))) + + +@XFAIL def test_sympy__tensor__tensor__TensorType(): from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, TensorType Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') sym = TensorSymmetry(get_symmetric_group_sgs(1)) assert _test_args(TensorType([Lorentz], sym)) +@XFAIL def test_sympy__tensor__tensor__TensorHead(): from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, TensorHead Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -2460,6 +2473,12 @@ def test_sympy__tensor__tensor__TensorHead(): S1 = TensorType([Lorentz], sym) assert _test_args(TensorHead('p', S1, 0)) +@XFAIL +def test_sympy__tensor__tensor__TensorIndex(): + from sympy.tensor.tensor import TensorIndexType, TensorIndex, TensorSymmetry, TensorType, get_symmetric_group_sgs + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + assert _test_args(TensorIndex('i', Lorentz)) + @SKIP("abstract class") def test_sympy__tensor__tensor__TensExpr(): pass @@ -2476,16 +2495,16 @@ def test_sympy__tensor__tensor__TensAdd(): assert _test_args(TensAdd(t1, t2)) -def test_sympy__tensor__tensor__Tensor(): +def test_sympy__tensor__tensor__TensMul(): from sympy.core import S - from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, tensor_indices, Tensor + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, tensor_indices, TensMul Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b = tensor_indices('a,b', Lorentz) sym = TensorSymmetry(get_symmetric_group_sgs(1)) S1 = TensorType([Lorentz], sym) p = S1('p') - free, dum = Tensor.from_indices(a) - assert _test_args(Tensor(S.One, [p], free, dum)) + free, dum = TensMul.from_indices(a) + assert _test_args(TensMul(S.One, [p], free, dum)) diff --git a/sympy/printing/str.py b/sympy/printing/str.py index cc4ee5a00b25..8e89b727974d 100644 --- a/sympy/printing/str.py +++ b/sympy/printing/str.py @@ -335,7 +335,10 @@ def _print_Permutation(self, expr): use = trim return 'Permutation(%s)' % use - def _print_Tensor(self, expr): + def _print_TensorIndex(self, expr): + return expr._pretty() + + def _print_TensMul(self, expr): return expr._pretty() def _print_TensAdd(self, expr): diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index c2507fe56d70..f26d9a6387c9 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -1,7 +1,7 @@ from sympy import Symbol, S, I from sympy.combinatorics import Permutation from sympy.tensor.tensor import (TensorIndexType, tensor_indices, - TensorSymmetry, get_symmetric_group_sgs, TensorType, tensor_mul, Tensor, + TensorSymmetry, get_symmetric_group_sgs, TensorType, tensor_mul, TensMul, TensAdd, TensorHead, tensorlist_contract_metric) @@ -51,7 +51,7 @@ def __init__(self, typ, gctr=4, g5c=I): sym0 = TensorSymmetry(([], [Permutation(1)])) S0 = TensorType([], sym0) self.Gamma5 = S0('G5', None) - self.G5 = Tensor(S.One, [self.Gamma5], [], []) + self.G5 = TensMul(S.One, [self.Gamma5], [], []) self.g5c = g5c self.epsilon = typ.epsilon self.eps_dim = typ.eps_dim @@ -124,7 +124,7 @@ def rule1_gamma(self, t, n, r=None): >>> from sympy import Symbol, S >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ - TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul) >>> from sympy.tensor.dgamma_matr import GammaMatrices >>> D = Symbol('D') >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') @@ -152,12 +152,12 @@ def rule1_gamma(self, t, n, r=None): if n == 0: # G(m)*G(-m) = d t1 = tensor_mul(*a1) - t2 = Tensor(D*t1._coeff*tc, t1._components, t1._free, t1._dum) + t2 = TensMul(D*t1._coeff*tc, t1._components, t1._free, t1._dum) return t2 if n == 1: # G(m)*G(n)*G(-m) = (2 - d)*G(n) t1 = tensor_mul(*a1) - t2 = Tensor((2-D)*t1._coeff*tc, t1._components, t1._free, t1._dum) + t2 = TensMul((2-D)*t1._coeff*tc, t1._components, t1._free, t1._dum) return t2 if n == 2: # G(m)*G(n1)*G(n2)*G(-m) = (d-4)*G(n1)*G(n2) + 4*delta(n1, n2) @@ -206,7 +206,7 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): >>> from sympy import Symbol, S >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ - TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul) >>> from sympy.tensor.dgamma_matr import GammaMatrices >>> D = Symbol('D') >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') @@ -256,7 +256,7 @@ def match2_gamma(self, t, n): p_pos2 = dx[3] if p_pos1 > 0 and p_pos2 > 0: if components[p_pos1] == components[p_pos2] \ - and components[p_pos1].commuting == 0: + and components[p_pos1].anticomm == 0: return i, p_pos1, p_pos2 return None @@ -272,7 +272,7 @@ def rule2_gamma(self, t, n): >>> from sympy import Symbol, S >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ - TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul) >>> from sympy.tensor.dgamma_matr import GammaMatrices >>> D = Symbol('D') >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') @@ -338,7 +338,7 @@ def do_rule2_gamma(self, t, nmax=1): >>> from sympy import Symbol, S >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ - TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul) >>> from sympy.tensor.dgamma_matr import GammaMatrices >>> D = Symbol('D') >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') @@ -374,7 +374,7 @@ def gamma_trace(self, t): >>> from sympy import Symbol, S >>> from sympy.tensor.tensor import (TensorIndexType, tensor_indices,\ - TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) + TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul) >>> from sympy.tensor.dgamma_matr import GammaMatrices >>> D = Symbol('D') >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') @@ -406,7 +406,7 @@ def gamma_trace(self, t): g = self.g if all(x != G for x in components): if any(x == Gamma5 for x in components): - return Tensor(S.Zero, [], [], []) + return TensMul(S.Zero, [], [], []) return self.gctr*t withG5 = False t = t.canon_bp() @@ -424,7 +424,7 @@ def gamma_trace(self, t): withG5 = True if withG5: if numG < 4 or numG % 2 != self.eps_dim % 2: - return Tensor(S.Zero, [], [], []) + return TensMul(S.Zero, [], [], []) if numG == 4: a = t.split() indices = [x._free[0][0] for x in a[i:j]] @@ -436,7 +436,7 @@ def gamma_trace(self, t): res = res.canon_bp() return res if numG > 4: - prev = Tensor(S.Zero, [], [], []) + prev = TensMul(S.Zero, [], [], []) t2 = t while True: t2 = self.do_rule1_gamma(t2) @@ -465,9 +465,9 @@ def gamma_trace(self, t): else: # without G5 if numG % 2 == 1: - return Tensor(S.Zero, [], [], []) + return TensMul(S.Zero, [], [], []) if numG > 4: - prev = Tensor(S.Zero, [], [], []) + prev = TensMul(S.Zero, [], [], []) t2 = t while True: t2 = self.do_rule1_gamma(t2) @@ -515,7 +515,7 @@ def gamma_trace1(self, *a): return self.gctr n = len(a) if n%2 == 1: - return Tensor(S.Zero, [], [], []) + return TensMul(S.Zero, [], [], []) if n == 2: ind0 = a[0]._free[0][0] ind1 = a[1]._free[0][0] diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index e262219f09c6..c5e4cede0fbe 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -1,25 +1,40 @@ from collections import defaultdict from sympy.core import Basic, sympify, Add, Mul, S +from sympy.core.symbol import Symbol, symbols from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs -class TensorIndexType(object): + +class TensorIndexType(Basic): """ - A TensorIndexType is characterized by its name, by metric_sym, + A TensorIndexType is characterized by its name, by metric_antisym, giving the symmetry of its metric. + ``metric_antisym = False`` symmetric metric (in Riemannian geometry) + + ``metric_antisym = True`` antisymmetric metric (for spinor calculus) + + In these two cases the metric is used to raise and lower indices. + + ``metric_antisym = None`` there is no metric; + it is not possible to raise or lower indices; + e.g. the index of the defining representation of ``SU(N)`` + is 'covariant' and the conjugate representation is + 'contravariant'; for ``N > 2`` they are linearly independent. + + If a dimension ``dim`` is defined, it can be a symbol or an integer. """ - def __init__(self, name, metric_sym=0, dim=None, eps_dim = None, + def __new__(cls, name, metric_antisym=False, dim=None, eps_dim = None, dummy_fmt=None): """ name name of the tensor type - ``metric_sym``: - 0 symmetric - 1 antisymmetric - None no symmetry + ``metric_antisym``: + False symmetric + True antisymmetric + None no symmetry - dim dimension, it can be a symbol or a positive integer + ``dim`` dimension, it can be a symbol or a positive integer ``eps_dim`` dimension of the epsilon tensor; it is by default equal to dim, if the latter is an integer; else @@ -35,28 +50,25 @@ def __init__(self, name, metric_sym=0, dim=None, eps_dim = None, >>> from sympy.tensor.tensor import TensorIndexType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') """ - self.name = name - self.metric_sym = metric_sym + obj = Basic.__new__(cls, name, metric_antisym) if not dummy_fmt: - self.dummy_fmt = '%s_%%d' % self.name - else: - self.dummy_fmt = '%s_%%d' % dummy_fmt - self.metric = self.get_metric() - self.dim = dim - self.delta = self.get_kronecker_delta() - if eps_dim: - self.eps_dim = eps_dim - else: - self.eps_dim = dim - if isinstance(self.eps_dim, int): - self.epsilon = self.get_epsilon() + obj.dummy_fmt = '%s_%%d' % obj.name else: - self.epsilon = None + obj.dummy_fmt = '%s_%%d' % dummy_fmt + obj.metric = obj.get_metric() + obj.dim = dim + obj.delta = obj.get_kronecker_delta() + obj.eps_dim = eps_dim if eps_dim else dim + obj.epsilon = obj.get_epsilon() + return obj + + name = property(lambda self: self.args[0]) + metric_antisym = property(lambda self: self.args[1]) def get_metric(self): - if self.metric_sym is None: + if self.metric_antisym is None: return None - sym2 = TensorSymmetry(get_symmetric_group_sgs(2, self.metric_sym)) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2, self.metric_antisym)) S2 = TensorType([self]*2, sym2) metric = S2('metric') return metric @@ -68,6 +80,8 @@ def get_kronecker_delta(self): return delta def get_epsilon(self): + if not isinstance(self.eps_dim, int): + return None sym = TensorSymmetry(get_symmetric_group_sgs(self.eps_dim, 1)) Sdim = TensorType([self]*self.eps_dim, sym) epsilon = Sdim('Eps') @@ -81,7 +95,7 @@ def __str__(self): __repr__ = __str__ -class TensorIndex(object): +class TensorIndex(Basic): """ Tensor indices are contructed with the Einstein summation convention. @@ -107,27 +121,20 @@ class TensorIndex(object): >>> A(i)*B(-i) A(L_0)*B(-L_0) """ - def __init__(self, name, tensortype, is_contravariant=True): - self.name = name - self.tensortype = tensortype - self.is_contravariant = is_contravariant + def __new__(cls, name, tensortype, is_contravariant=True): - def __str__(self): + obj = Basic.__new__(cls, name, tensortype, is_contravariant) + obj.name = name + obj.tensortype = tensortype + obj.is_contravariant = is_contravariant + return obj + + def _pretty(self): s = self.name if not self.is_contravariant: s = '-%s' % s return s - __repr__ = __str__ - - def __eq__(self, other): - return self.name == other.name and \ - self.tensortype == other.tensortype and \ - self.is_contravariant == other.is_contravariant - - def __ne__(self, other): - return not (self == other) - def __lt__(self, other): return (self.tensortype, self.name) < (other.tensortype, other.name) @@ -138,7 +145,7 @@ def __neg__(self): def tensor_indices(s, typ): """ - Returns tensor indices given their names and the TensorIndexType ``typ`` + Returns list of tensor indices given their names and the type ``typ`` ``s`` string of comma separated names of indices @@ -151,11 +158,15 @@ def tensor_indices(s, typ): >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) """ - a = s.split(',') + if isinstance(s, str): + a = [x.name for x in symbols(s, seq=True)] + else: + raise ValueError('expecting a string') + return [TensorIndex(i, typ) for i in a] -class TensorSymmetry(object): +class TensorSymmetry(Basic): """ Symmetry of a tensor @@ -173,9 +184,15 @@ class TensorSymmetry(object): >>> S2 = TensorType([Lorentz]*2, sym2) >>> V = S2('V') """ - def __init__(self, bsgs): - self.base, self.generators = bsgs - self.rank = self.generators[0].size + def __new__(cls, bsgs, **kw_args): + base, generators = bsgs + obj = Basic.__new__(cls, base, generators, **kw_args) + return obj + + base = property(lambda self: self.args[0]) + generators = property(lambda self: self.args[1]) + rank = property(lambda self: self.args[1][0].size) + class TensorType(Basic): @@ -197,24 +214,28 @@ class TensorType(Basic): is_commutative = False def __new__(cls, index_types, symmetry, **kw_args): - obj = Basic.__new__(cls, **kw_args) - obj.index_types = [] - obj.index_types = index_types - obj.types = list(set(obj.index_types)) - obj.types.sort(key=lambda x: x.name) - obj.symmetry = symmetry assert symmetry.rank == len(index_types) + 2 + obj = Basic.__new__(cls, index_types, symmetry, **kw_args) return obj + index_types = property(lambda self: self.args[0]) + + symmetry = property(lambda self: self.args[1]) + + types = property(lambda self: sorted(set(self.index_types), key=lambda x: x.name)) + def __str__(self): return 'TensorType(%s)' %([str(x) for x in self.index_types]) - def __call__(self, s, commuting=0): + def __call__(self, s, anticommuting=False): """ - commuting: - None no commutation rule - 0 commutes - 1 anticommutes + Return a TensorHead object or a list of TensorHead objects. + + ``s`` name or string of names + ``anticommuting``: + None no commutation rule + False commutes + True anticommutes Examples ======== @@ -235,16 +256,19 @@ def __call__(self, s, commuting=0): >>> canon_bp(W(a, b)*W(-b, -a)) 0 """ - names = s.split(',') + if isinstance(s, str): + names = [x.name for x in symbols(s, seq=True)] + else: + raise ValueError('expecting a string') if len(names) == 1: - return TensorHead(names[0], self, commuting) + return TensorHead(names[0], self, anticommuting) else: - return [TensorHead(name, self, commuting) for name in names] + return [TensorHead(name, self, anticommuting) for name in names] class TensorHead(Basic): is_commutative = False - def __new__(cls, name, typ, commuting, **kw_args): + def __new__(cls, name, typ, anticommuting, **kw_args): """ tensor with given name, index types, symmetry, commutation rule @@ -252,10 +276,10 @@ def __new__(cls, name, typ, commuting, **kw_args): ``typ`` list of TensorIndexType - ``commuting`` commutation property - 0 commuting tensor - 1 anticommuting tensor - None no commutation rule + ``anticommuting`` commutation property + False commuting tensor + True anticommuting tensor + None no commutation rule Examples ======== @@ -269,22 +293,15 @@ def __new__(cls, name, typ, commuting, **kw_args): """ assert isinstance(name, basestring) - obj = Basic.__new__(cls, **kw_args) - obj.name = name - obj.index_types = typ.index_types + obj = Basic.__new__(cls, name, typ, **kw_args) obj.rank = len(obj.index_types) obj.types = typ.types obj.symmetry = typ.symmetry - obj.commuting = commuting + obj.anticomm = anticommuting return obj - def __eq__(self, other): - if not isinstance(other, TensorHead): - return False - return self.name == other.name and self.index_types == other.index_types - - def __ne__(self, other): - return not (self == other) + name = property(lambda self: self.args[0]) + index_types = property(lambda self: self.args[1].index_types) def __lt__(self, other): return (self.name, self.index_types) < (other.name, other.index_types) @@ -298,9 +315,9 @@ def commutes_with(self, other): TODO: it should be possible to assign rules for commutations between tensors, to be used here. """ - if self.commuting == 0 or other.commuting == 0: + if self.anticomm == 0 or other.anticomm == 0: return 0 - if self.commuting == 1 and other.commuting == 1: + if self.anticomm == 1 and other.anticomm == 1: return 1 return None @@ -326,10 +343,10 @@ def __call__(self, *indices): """ assert self.rank == len(indices) components = [self] - free, dum = Tensor.from_indices(*indices) + free, dum = TensMul.from_indices(*indices) free.sort(key=lambda x: x[0].name) dum.sort() - return Tensor(S.One, components, free, dum) + return TensMul(S.One, components, free, dum) class TensExpr(Basic): @@ -337,12 +354,12 @@ class TensExpr(Basic): A tensor expression is an expression formed by tensors; currently the sums of tensors are distributed. - A TensExpr can be a TensAdd or a Tensor. + A TensExpr can be a TensAdd or a TensMul. TensAdd objects are put in canonic form using the Butler-Portugal algorithm for canonicalization under monoterm symmetries. - Tensor objects are formed by products of component tensors, + TensMul objects are formed by products of component tensors, and include a coefficient, which is a SymPy expression. @@ -389,12 +406,12 @@ def __mul__(self, other): return TensAdd(*[x*other for x in self.args]) if other.is_TensAdd: return TensAdd(*[self*x for x in other.args]) - return Tensor.__mul__(self, other) + return TensMul.__mul__(self, other) def __rmul__(self, other): if self.is_TensMul: coeff = other*self._coeff - return Tensor(coeff, self._components, self._free, self._dum) + return TensMul(coeff, self._components, self._free, self._dum) return TensAdd(*[x*other for x in self.args]) def __pow__(self, other): @@ -408,7 +425,7 @@ def __div__(self, other): if other.is_Tensor: raise ValueError('cannot divide by a tensor') coeff = self._coeff/other - return Tensor(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) + return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) def __rdiv__(self, other): @@ -450,7 +467,7 @@ def substitute_indices(self, *index_tuples): else: free1.append((j, ipos, cpos)) - return Tensor(self._coeff, self._components, free1, self._dum) + return TensMul(self._coeff, self._components, free1, self._dum) if self.is_TensAdd: args = self.args args1 = [] @@ -459,6 +476,89 @@ def substitute_indices(self, *index_tuples): args1.append(y) return TensAdd(*args1) +def _tensAdd_collect_terms(args): + """ + collect TensMul terms differing at most by their coefficient + """ + a = [] + pprev = None + prev = args[0] + prev_coeff = prev._coeff + changed = False + new = 0 + + for x in args[1:]: + # if x and prev have the same tensor, update the coeff of prev + if x._components == prev._components \ + and x._free == prev._free and x._dum == prev._dum: + prev_coeff = prev_coeff + x._coeff + changed = True + op = 0 + else: + # x and prev are different; if not changed, prev has not + # been updated; store it + if not changed: + a.append(prev) + else: + # get a tensor from prev with coeff=prev_coeff and store it + if prev_coeff: + t = TensMul(prev_coeff, prev._components, + prev._free, prev._dum) + a.append(t) + # move x to prev + op = 1 + pprev, prev = prev, x + pprev_coeff, prev_coeff = prev_coeff, x._coeff + changed = False + # if the case op=0 prev was not stored; store it now + # in the case op=1 x was not stored; store it now (as prev) + if op == 0 and prev_coeff: + prev = TensMul(prev_coeff, prev._components, prev._free, prev._dum) + a.append(prev) + elif op == 1: + a.append(prev) + return a + +def _tensAdd_flatten(args): + """ + flatten TensAdd, coerce terms which are not tensors to tensors + """ + if not all(x.is_Tensor for x in args): + args1 = [] + for x in args: + if x.is_Tensor: + if x.is_TensAdd: + args1.extend(list(x.args)) + else: + args1.append(x) + args1 = [x for x in args1 if x.is_Tensor and x._coeff] + args2 = [x for x in args if not x.is_Tensor] + t0 = args1[0] + if t0.is_TensAdd: + t0 = t0.args[0] + if t0._free: + raise ValueError('all tensors must have the same indices') + t1 = TensMul(Add(*args2), [], [], []) + args = [t1] + args1 + a = [] + for x in args: + if x.is_TensAdd: + a.extend(list(x.args)) + else: + a.append(x) + args = [x for x in a if x._coeff] + return args + +def _tensAdd_check(args): + """ + check that all addends have the same free indices + """ + indices0 = sorted([x[0] for x in args[0]._free], key=lambda x: x.name) + list_indices = [sorted([y[0] for y in x._free], key=lambda x: x.name) for x in args[1:]] + if not all(x == indices0 for x in list_indices): + raise ValueError('all tensors must have the same indices') + + class TensAdd(TensExpr): """ Sum of tensors @@ -487,87 +587,21 @@ def __new__(cls, *args, **kw_args): args tuple of addends """ args = [sympify(x) for x in args if x] - if not all(x.is_Tensor for x in args): - args1 = [] - for x in args: - if x.is_Tensor: - if x.is_TensAdd: - args1.extend(list(x.args)) - else: - args1.append(x) - args1 = [x for x in args1 if x.is_Tensor and x._coeff] - args2 = [x for x in args if not x.is_Tensor] - t0 = args1[0] - if t0.is_TensAdd: - t0 = t0.args[0] - if t0._free: - raise ValueError('all tensors must have the same indices') - t1 = Tensor(Add(*args2), [], [], []) - args = [t1] + args1 - a = [] - for x in args: - if x.is_TensAdd: - a.extend(list(x.args)) - else: - a.append(x) - args = a - args = [x for x in args if x._coeff] + args = _tensAdd_flatten(args) if not args: return S.Zero - indices0 = sorted([x[0] for x in args[0]._free], key=lambda x: x.name) - list_indices = [sorted([y[0] for y in x._free], key=lambda x: x.name) for x in args[1:]] - if not all(x == indices0 for x in list_indices): - raise ValueError('all tensors must have the same indices') - + _tensAdd_check(args) obj = Basic.__new__(cls, **kw_args) - # collect terms - args.sort(key=lambda x: (x._components, x._free, x._dum)) - a = [] - pprev = None - prev = args[0] - prev_coeff = prev._coeff - changed = False - new = 0 if len(args) == 1 and args[0].is_TensMul: obj._args = tuple(args) return obj - - for x in args[1:]: - # if x and prev have the same tensor, update the coeff of prev - if x._components == prev._components \ - and x._free == prev._free and x._dum == prev._dum: - prev_coeff = prev_coeff + x._coeff - changed = True - op = 0 - else: - # x and prev are different; if not changed, prev has not - # been updated; store it - if not changed: - a.append(prev) - else: - # get a tensor from prev with coeff=prev_coeff and store it - if prev_coeff: - t = Tensor(prev_coeff, prev._components, - prev._free, prev._dum) - a.append(t) - # move x to prev - op = 1 - pprev, prev = prev, x - pprev_coeff, prev_coeff = prev_coeff, x._coeff - changed = False - # if the case op=0 prev was not stored; store it now - # in the case op=1 x was not stored; store it now (as prev) - if op == 0 and prev_coeff: - prev = Tensor(prev_coeff, prev._components, prev._free, prev._dum) - a.append(prev) - elif op == 1: - a.append(prev) + args.sort(key=lambda x: (x._components, x._free, x._dum)) + a = _tensAdd_collect_terms(args) if not a: return S.Zero - # TODO introduce option not to use canon_bp automatically in TensAdd - if all(x.is_TensMul for x in a): - a = [canon_bp(x) for x in a] + + a = [canon_bp(x) for x in a] a = [x for x in a if x] if not a: return S.Zero @@ -610,7 +644,6 @@ def contract_delta(self, delta): t = TensAdd(*args) return t.canon_bp() - def contract_metric(self, g, contract_all=False): """ Raise or lower indices with the metric ``g`` @@ -636,7 +669,7 @@ def _pretty(self): s = s.replace('+ -', '- ') return s -class Tensor(TensExpr): +class TensMul(TensExpr): """ Product of tensors """ @@ -699,10 +732,10 @@ def from_indices(*indices): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, Tensor, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, TensMul, get_symmetric_group_sgs >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) - >>> Tensor.from_indices(m0, m1, -m1, m3) + >>> TensMul.from_indices(m0, m1, -m1, m3) ([(m0, 0, 0), (m3, 3, 0)], [(1, 2, 0, 0)]) """ n = len(indices) @@ -821,7 +854,7 @@ def split(self): t1 = t(*indices[pos:pos + t.rank]) pos += t.rank res.append(t1) - res[0] = Tensor(self._coeff, res[0]._components, res[0]._free, res[0]._dum, is_canon_bp=res[0]._is_canon_bp) + res[0] = TensMul(self._coeff, res[0]._components, res[0]._free, res[0]._dum, is_canon_bp=res[0]._is_canon_bp) return res def canon_args(self): @@ -864,7 +897,7 @@ def canon_args(self): dummies.append(a) a = [pos, pos + 1] prev = typ - msym.append(typ.metric_sym) + msym.append(typ.metric_antisym) else: a.extend([pos, pos + 1]) pos += 2 @@ -880,7 +913,7 @@ def canon_args(self): numtyp.append([prev, 1]) v = [] for h, n in numtyp: - v.append((h.symmetry.base, h.symmetry.generators, n, h.commuting)) + v.append((h.symmetry.base, h.symmetry.generators, n, h.anticomm)) return _af_new(g), dummies, msym, v def __mul__(self, other): @@ -908,7 +941,7 @@ def __mul__(self, other): other = sympify(other) if not other.is_Tensor: coeff = self._coeff*other - return Tensor(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) + return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) if other.is_TensAdd: return TensAdd(*[self*x for x in other.args]) @@ -937,7 +970,7 @@ def __mul__(self, other): new_dummy = (ipos2, ipos1, cpos2, cpos1) dum.append(new_dummy) coeff = self._coeff*other._coeff - return Tensor(coeff, components, free, dum) + return TensMul(coeff, components, free, dum) def sorted_components(self): @@ -970,7 +1003,7 @@ def sorted_components(self): coeff = -self._coeff else: coeff = self._coeff - t = Tensor(coeff, components, free, dum) + t = TensMul(coeff, components, free, dum) return t def perm2tensor(self, g, canon_bp=False): @@ -1020,7 +1053,7 @@ def perm2tensor(self, g, canon_bp=False): coeff = self._coeff if g[-1] != len(g) - 1: coeff = -coeff - res = Tensor(coeff, components, free, dum, is_canon_bp=canon_bp) + res = TensMul(coeff, components, free, dum, is_canon_bp=canon_bp) return res def canon_bp(self): @@ -1056,7 +1089,6 @@ def canon_bp(self): return S.Zero return t.perm2tensor(can, True) - def contract_delta(self, delta): if not self._components: return self @@ -1085,7 +1117,6 @@ def contract_delta(self, delta): break else: return self - # tg has one or two indices contracted with other tensors # i position of tg in a coeff = S.One @@ -1109,7 +1140,7 @@ def contract_delta(self, delta): free1.append((ind_free, iposx, 0)) else: free1.append((indx, iposx, 0)) - t1 = Tensor(tx._coeff, tx._components, free1, tx._dum) + t1 = TensMul(tx._coeff, tx._components, free1, tx._dum) a[j] = t1 a = a[:i] + a[i + 1:] coeff = coeff*tg._coeff @@ -1135,7 +1166,7 @@ def contract_delta(self, delta): res = (coeff*typ.dim*tg._coeff*ty._coeff)*res else: res = coeff*typ.dim*tg._coeff*ty._coeff - res = Tensor(res, [],[],[], is_canon_bp=True) + res = TensMul(res, [],[],[], is_canon_bp=True) if delta in res._components: return res.contract_delta(delta) return res @@ -1146,7 +1177,7 @@ def contract_delta(self, delta): free2.append((ind1, iposx, 0)) else: free2.append((indx, iposx, 0)) - t2 = Tensor(ty._coeff, ty._components, free2, ty._dum) + t2 = TensMul(ty._coeff, ty._components, free2, ty._dum) a[k] = t2 a = a[:i] + a[i + 1:] coeff = coeff*tg._coeff @@ -1156,7 +1187,6 @@ def contract_delta(self, delta): return res.contract_delta(delta) return res - def contract_metric(self, g, contract_all=False): """ Raise or lower indices with the metric ``g`` @@ -1181,9 +1211,7 @@ def contract_metric(self, g, contract_all=False): >>> t.contract_metric(g).canon_bp() p(L_0)*q(-L_0) """ - delta = g.index_types[0].delta - self = self.contract_delta(delta) - if g.index_types[0].metric_sym != 0: + if g.index_types[0].metric_antisym != 0: # TODO case of antisymmetric metric raise NotImplementedError if not self._components: @@ -1235,7 +1263,7 @@ def contract_metric(self, g, contract_all=False): free1.append((ind_free, iposx, 0)) else: free1.append((indx, iposx, 0)) - t1 = Tensor(tx._coeff, tx._components, free1, tx._dum) + t1 = TensMul(tx._coeff, tx._components, free1, tx._dum) a[j] = t1 a = a[:i] + a[i + 1:] coeff = coeff*tg._coeff @@ -1261,7 +1289,7 @@ def contract_metric(self, g, contract_all=False): res = (coeff*typ.dim*tg._coeff*ty._coeff)*res else: res = coeff*typ.dim*tg._coeff*ty._coeff - res = Tensor(res, [],[],[], is_canon_bp=True) + res = TensMul(res, [],[],[], is_canon_bp=True) if contract_all == True and g in res._components: return res.contract_metric(g, True) return res @@ -1272,7 +1300,7 @@ def contract_metric(self, g, contract_all=False): free2.append((ind1, iposx, 0)) else: free2.append((indx, iposx, 0)) - t2 = Tensor(ty._coeff, ty._components, free2, ty._dum) + t2 = TensMul(ty._coeff, ty._components, free2, ty._dum) a[k] = t2 a = a[:i] + a[i + 1:] coeff = coeff*tg._coeff @@ -1320,7 +1348,7 @@ def tensor_mul(*a): product of tensors """ if not a: - return Tensor(S.One, [], [], []) + return TensMul(S.One, [], [], []) t = a[0] for tx in a[1:]: t = t*tx @@ -1341,7 +1369,7 @@ def tensorlist_contract_metric(a, tg): ind3 = ind2 if indx == mind1 else ind1 free1 = t1._free[:] free1[j] = (ind3, ipos, 0) - t2 = Tensor(t1._coeff, t1._components, free1, t1._dum) + t2 = TensMul(t1._coeff, t1._components, free1, t1._dum) a[i] = t2 return a a.append(tg) diff --git a/sympy/tensor/tests/test_dgamma_matr.py b/sympy/tensor/tests/test_dgamma_matr.py index ae2282415a9b..689f8b35a0a7 100644 --- a/sympy/tensor/tests/test_dgamma_matr.py +++ b/sympy/tensor/tests/test_dgamma_matr.py @@ -1,6 +1,6 @@ from sympy import Symbol, S, I from sympy.tensor.tensor import (TensorIndexType, tensor_indices, \ -TensorSymmetry, get_symmetric_group_sgs, TensorType, Tensor) +TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul) from sympy.tensor.dgamma_matr import GammaMatrices from sympy.utilities.pytest import skip, XFAIL diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index ef03eee0a6a9..6bbbfb721c83 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -4,24 +4,10 @@ from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, - tensorlist_contract_metric, Tensor) + tensorlist_contract_metric, TensMul) +from sympy.utilities.pytest import raises -def test_get_indices(): - Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - a, b, c, d = tensor_indices('a,b,c,d', Lorentz) - assert a != -a - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) - S2 = TensorType([Lorentz]*2, sym2) - A, B = S2('A,B') - assert A != B - t = A(a,b)*B(-b,c) - indices = t.get_indices() - L_0 = TensorIndex('L_0', Lorentz) - assert indices == [a, L_0, -L_0, c] - a = t.split() - t2 = tensor_mul(*a) - assert t == t2 - assert tensor_mul(*[]) == Tensor(S.One, [],[],[]) +#################### Tests from tensor_can.py ####################### def test_canonicalize_no_slot_sym(): # A_d0 * B^d0; T_c = A^d0*B_d0 @@ -65,7 +51,7 @@ def test_canonicalize_no_slot_sym(): nsym2 = TensorSymmetry(([], [Permutation(range(4))])) NS2 = TensorType([Lorentz]*2, nsym2) A = NS2('A') - B, C = S1('B,C') + B, C = S1('B, C') t = A(d1, -d0)*B(d0)*C(-d1) tc = t.canon_bp() assert str(tc) == 'A(L_0, L_1)*B(-L_1)*C(-L_0)' @@ -109,7 +95,7 @@ def test_canonicalize_no_slot_sym(): def test_canonicalize_no_dummies(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + a, b, c, d = tensor_indices('a, b, c, d', Lorentz) sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) @@ -158,8 +144,8 @@ def test_no_metric_symmetry(): # no metric symmetry; A no symmetry # A^d1_d0 * A^d0_d1 # T_c = A^d0_d1 * A^d1_d0 - Lorentz = TensorIndexType('Lorentz', metric_sym=None, dummy_fmt='L') - d0, d1, d2, d3 = tensor_indices('d0,d1,d2,d3', Lorentz) + Lorentz = TensorIndexType('Lorentz', metric_antisym=None, dummy_fmt='L') + d0, d1, d2, d3 = tensor_indices('d0 d1 d2 d3', Lorentz) nsym2 = TensorSymmetry(([], [Permutation(range(4))])) NS2 = TensorType([Lorentz]*2, nsym2) A = NS2('A') @@ -270,7 +256,7 @@ def test_canonicalize1(): # A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} - Spinor = TensorIndexType('Spinor', metric_sym=1, dummy_fmt='S') + Spinor = TensorIndexType('Spinor', metric_antisym=1, dummy_fmt='S') a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) S3 = TensorType([Spinor]*3, sym3) @@ -285,7 +271,7 @@ def test_canonicalize1(): # no metric symmetry # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 - Mat = TensorIndexType('Mat', metric_sym=None, dummy_fmt='M') + Mat = TensorIndexType('Mat', metric_antisym=None, dummy_fmt='M') a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) S3 = TensorType([Mat]*3, sym3) @@ -407,6 +393,49 @@ def test_riemann_products(): t = R(d2, a0, a2, d0)*R(d1, -d2, a1, a3)*R(a4, a5, -d0, -d1) tc = t.canon_bp() assert str(tc) == 'R(a0, L_0, a2, L_1)*R(a1, a3, -L_0, L_2)*R(a4, a5, -L_1, -L_2)' +###################################################################### + +def test_canonicalize2(): + D = Symbol('D') + Eucl = TensorIndexType('Eucl', metric_antisym=0, dim=D, dummy_fmt='E') + i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14 = \ + tensor_indices('i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14', Eucl) + sym3a = TensorSymmetry(get_symmetric_group_sgs(3, 1)) + S3a = TensorType([Eucl]*3, sym3a) + A = S3a('A') + + # two examples from Cvitanovic, Group Theory page 59 + # of identities for antisymmetric tensors of rank 3 + # contracted according to the Kuratowski graph eq.(6.59) + t = A(i0,i1,i2)*A(-i1,i3,i4)*A(-i3,i7,i5)*A(-i2,-i5,i6)*A(-i4,-i6,i8) + t1 = t.canon_bp() + assert t1 == 0 + + # eq.(6.60) + #t = A(i0,i1,i2)*A(-i1,i3,i4)*A(-i2,i5,i6)*A(-i3,i7,i8)*A(-i6,-i7,i9)* + # A(-i8,i10,i13)*A(-i5,-i10,i11)*A(-i4,-i11,i12)*A(-i3,-i12,i14) + t = A(i0,i1,i2)*A(-i1,i3,i4)*A(-i2,i5,i6)*A(-i3,i7,i8)*A(-i6,-i7,i9)*\ + A(-i8,i10,i13)*A(-i5,-i10,i11)*A(-i4,-i11,i12)*A(-i9,-i12,i14) + t1 = t.canon_bp() + assert t1 == 0 + + +def test_get_indices(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + assert a != -a + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([Lorentz]*2, sym2) + A, B = S2('A B') + assert A != B + t = A(a,b)*B(-b,c) + indices = t.get_indices() + L_0 = TensorIndex('L_0', Lorentz) + assert indices == [a, L_0, -L_0, c] + a = t.split() + t2 = tensor_mul(*a) + assert t == t2 + assert tensor_mul(*[]) == TensMul(S.One, [],[],[]) def test_add1(): @@ -648,7 +677,6 @@ def test_metric_contract1(): assert t1 == 2*D*p(a) t = 2*p(a)*g(b,-a) - #t = 2*p(a)*g(-a, b) t1 = t.contract_metric(g) assert t1 == 2*p(b) From 5e3a9a6bfcd9ca1de17df343a7f8ef12d8124ed1 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 25 Dec 2012 10:34:38 +0100 Subject: [PATCH 16/47] eliminated `is_Tensor` from Basic --- sympy/core/basic.py | 1 - sympy/tensor/dgamma_matr.py | 10 +++++----- sympy/tensor/tensor.py | 29 ++++++++++++++--------------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/sympy/core/basic.py b/sympy/core/basic.py index fc41e9f41717..012ed3632607 100644 --- a/sympy/core/basic.py +++ b/sympy/core/basic.py @@ -70,7 +70,6 @@ class Basic(object): is_Boolean = False is_Not = False is_Matrix = False - is_Tensor = False @property @deprecated(useinstead="is_Float", issue=1721, deprecated_since_version="0.7.0") diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index f26d9a6387c9..8c3ccb4a8fbd 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -1,6 +1,6 @@ from sympy import Symbol, S, I from sympy.combinatorics import Permutation -from sympy.tensor.tensor import (TensorIndexType, tensor_indices, +from sympy.tensor.tensor import (is_Tensor, TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, tensor_mul, TensMul, TensAdd, TensorHead, tensorlist_contract_metric) @@ -61,7 +61,7 @@ def G5_to_right(self, t): """ move G5 to the right """ - if not t.is_Tensor: + if not is_Tensor(t): return t if t.is_TensAdd: a = [self.G5_to_right(x) for x in t.args] @@ -219,7 +219,7 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): >>> GM.do_rule1_gamma(t, doall=True) D*(-D + 2) """ - if not t.is_Tensor: + if not is_Tensor(t): return t if t.is_TensMul: for n in range(nmax + 1): @@ -354,7 +354,7 @@ def do_rule2_gamma(self, t, nmax=1): >>> GM.do_rule2_gamma(t) (-M**2)*G(L_0)*G(-L_0) + G(L_0)*G(-L_0)*p(L_1)*p(-L_1) """ - if not t.is_Tensor: + if not is_Tensor(t): return t if t.is_TensMul: for n in range(nmax + 1): @@ -392,7 +392,7 @@ def gamma_trace(self, t): >>> gamma_trace(t) -4*metric(m0, m2)*p(L_0)*q(-L_0) + 4*p(m0)*q(m2) + 4*p(m2)*q(m0) """ - if not t.is_Tensor: + if not is_Tensor(t): return self.gctr*t t = self.G5_to_right(t) if t.is_TensAdd: diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index c5e4cede0fbe..dd2ed73451f3 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -4,6 +4,9 @@ from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs +def is_Tensor(x): + return isinstance(x, TensExpr) + class TensorIndexType(Basic): """ A TensorIndexType is characterized by its name, by metric_antisym, @@ -372,8 +375,6 @@ class TensExpr(Basic): """ _op_priority = 11.0 - is_Tensor = True - is_TensExpr = True is_TensMul = False is_TensAdd = False is_commutative = False @@ -422,7 +423,7 @@ def __rpow__(self, other): def __div__(self, other): other = sympify(other) - if other.is_Tensor: + if is_Tensor(other): raise ValueError('cannot divide by a tensor') coeff = self._coeff/other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) @@ -523,16 +524,16 @@ def _tensAdd_flatten(args): """ flatten TensAdd, coerce terms which are not tensors to tensors """ - if not all(x.is_Tensor for x in args): + if not all(is_Tensor(x) for x in args): args1 = [] for x in args: - if x.is_Tensor: + if is_Tensor(x): if x.is_TensAdd: args1.extend(list(x.args)) else: args1.append(x) - args1 = [x for x in args1 if x.is_Tensor and x._coeff] - args2 = [x for x in args if not x.is_Tensor] + args1 = [x for x in args1 if is_Tensor(x) and x._coeff] + args2 = [x for x in args if not is_Tensor(x)] t0 = args1[0] if t0.is_TensAdd: t0 = t0.args[0] @@ -579,7 +580,6 @@ class TensAdd(TensExpr): >>> t1 + t2 p(a) + q(a) """ - is_Tensor = True is_TensAdd = True def __new__(cls, *args, **kw_args): @@ -620,13 +620,13 @@ def canon_bp(self): def __eq__(self, other): other = sympify(other) - if not other.is_Tensor: + if not is_Tensor(other): if len(self.args) == 1: return self.args[0]._coeff == other - if other.is_Tensor and other.is_TensMul and other._coeff == 0: + if is_Tensor(other) and other.is_TensMul and other._coeff == 0: return self == 0 t = self - other - if not t.is_Tensor: + if not is_Tensor(t): return t == 0 else: if t.is_TensMul: @@ -673,7 +673,6 @@ class TensMul(TensExpr): """ Product of tensors """ - is_Tensor = True is_TensMul = True def __new__(cls, coeff, *args, **kw_args): @@ -710,7 +709,7 @@ def __eq__(self, other): if other == 0: return self._coeff == 0 other = sympify(other) - if not other.is_Tensor : + if not is_Tensor(other): return False res = self - other return res == 0 @@ -939,7 +938,7 @@ def __mul__(self, other): p(L_0)*q(-L_0) """ other = sympify(other) - if not other.is_Tensor: + if not is_Tensor(other): coeff = self._coeff*other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) if other.is_TensAdd: @@ -1339,7 +1338,7 @@ def canon_bp(p): """ Butler-Portugal canonicalization """ - if p.is_Tensor: + if is_Tensor(p): return p.canon_bp() return p From 5cf087f74af41e280f5475020f319733f1ba6fd9 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 25 Dec 2012 19:10:52 +0100 Subject: [PATCH 17/47] taken away is_Tensor from Basic in TensorIndexType.__new__ replaced metric_antisym with metric factored `contract` from `contract_delta` and `contract_metric`; added tests for contract_delta --- sympy/core/tests/test_args.py | 3 +- sympy/tensor/tensor.py | 172 ++++++++---------------------- sympy/tensor/tests/test_tensor.py | 76 ++++++++++++- 3 files changed, 118 insertions(+), 133 deletions(-) diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py index 5b3d3529d11f..154b3e446f4c 100644 --- a/sympy/core/tests/test_args.py +++ b/sympy/core/tests/test_args.py @@ -2447,10 +2447,11 @@ def test_sympy__tensor__indexed__IndexedBase(): assert _test_args(IndexedBase('A', 1)) assert _test_args(IndexedBase('A')[0, 1]) +@XFAIL def test_sympy__tensor__tensor__TensorIndexType(): from sympy.tensor.tensor import TensorIndexType from sympy import Symbol - assert _test_args(TensorIndexType(Symbol('Lorentz'), metric_antisym=S.Zero)) + assert _test_args(TensorIndexType('Lorentz', metric=False)) @XFAIL def test_sympy__tensor__tensor__TensorSymmetry(): diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index dd2ed73451f3..7dec25fd4771 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -7,35 +7,39 @@ def is_Tensor(x): return isinstance(x, TensExpr) + class TensorIndexType(Basic): """ - A TensorIndexType is characterized by its name, by metric_antisym, - giving the symmetry of its metric. + A TensorIndexType is characterized by its name and its metric. - ``metric_antisym = False`` symmetric metric (in Riemannian geometry) + ``metric = False`` symmetric metric (in Riemannian geometry) - ``metric_antisym = True`` antisymmetric metric (for spinor calculus) + ``metric = True`` antisymmetric metric (for spinor calculus) In these two cases the metric is used to raise and lower indices. - ``metric_antisym = None`` there is no metric; + ``metric = None`` there is no metric; it is not possible to raise or lower indices; e.g. the index of the defining representation of ``SU(N)`` is 'covariant' and the conjugate representation is 'contravariant'; for ``N > 2`` they are linearly independent. + ``metric`` can be an object having ``name`` and ``antisym`` attributes. If a dimension ``dim`` is defined, it can be a symbol or an integer. """ - def __new__(cls, name, metric_antisym=False, dim=None, eps_dim = None, + def __new__(cls, name, metric=False, dim=None, eps_dim = None, dummy_fmt=None): """ name name of the tensor type - ``metric_antisym``: - False symmetric + ``metric``: it can be True, False, None or another object + If it is True, False, None it gives its antisymmetry: + False symmetric metric True antisymmetric - None no symmetry + None no metric + + Otherwise, ``metric`` must have attributes ``name`` and ``antisym`` ``dim`` dimension, it can be a symbol or a positive integer @@ -53,12 +57,24 @@ def __new__(cls, name, metric_antisym=False, dim=None, eps_dim = None, >>> from sympy.tensor.tensor import TensorIndexType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') """ - obj = Basic.__new__(cls, name, metric_antisym) + obj = Basic.__new__(cls, name, metric) if not dummy_fmt: obj.dummy_fmt = '%s_%%d' % obj.name else: obj.dummy_fmt = '%s_%%d' % dummy_fmt - obj.metric = obj.get_metric() + if metric is None: + obj.metric_antisym = None + else: + if metric in (True, False, 0, 1): + metric_name = 'metric' + obj.metric_antisym = metric + else: + metric_name = metric.name + obj.metric_antisym = metric.antisym + sym2 = TensorSymmetry(get_symmetric_group_sgs(2, obj.metric_antisym)) + S2 = TensorType([obj]*2, sym2) + obj.metric = S2(metric_name) + obj.dim = dim obj.delta = obj.get_kronecker_delta() obj.eps_dim = eps_dim if eps_dim else dim @@ -66,15 +82,7 @@ def __new__(cls, name, metric_antisym=False, dim=None, eps_dim = None, return obj name = property(lambda self: self.args[0]) - metric_antisym = property(lambda self: self.args[1]) - def get_metric(self): - if self.metric_antisym is None: - return None - sym2 = TensorSymmetry(get_symmetric_group_sgs(2, self.metric_antisym)) - S2 = TensorType([self]*2, sym2) - metric = S2('metric') - return metric def get_kronecker_delta(self): sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) @@ -1088,29 +1096,30 @@ def canon_bp(self): return S.Zero return t.perm2tensor(can, True) - def contract_delta(self, delta): + def contract(self, g, is_metric, contract_all=False): if not self._components: return self free_indices = [x[0] for x in self._free] a = self.split() - typ = delta.index_types[0] + typ = g.index_types[0] # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim for i, tx in enumerate(a): - if tx._components[0] == delta: + if tx._components[0] == g: free_indices_g = [x[0] for x in a[i]._free] if len(free_indices_g) == 0: a1 = a[:i] + a[i + 1:] t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) - if delta in t._components: - return t.contract_delta(delta) + if contract_all == True and g in t._components: + return t.contract(g, is_metric, True) return t # if all metric tensors have only free indices, there is no contraction for i, tg in enumerate(a): - if tg._components[0] == delta: + if tg._components[0] == g: tg_free_indices = [x[0] for x in tg._free] - if tg_free_indices[0].is_contravariant == tg_free_indices[1].is_contravariant: - raise ValueError('both indices are (contra)variant') + if not is_metric: + if tg_free_indices[0].is_contravariant == tg_free_indices[1].is_contravariant: + raise ValueError('both indices are (contra)variant') if all(indx in free_indices for indx in tg_free_indices): continue break @@ -1153,7 +1162,7 @@ def contract_delta(self, delta): for k, ty in enumerate(a): if ind2m in [x[0] for x in ty._free]: break - if ty._components == [delta]: + if ty._components == [g]: ty_indices = [x[0] for x in ty._free] if all(x in [ind1m, ind2m] for x in ty_indices): if i < k: @@ -1166,8 +1175,8 @@ def contract_delta(self, delta): else: res = coeff*typ.dim*tg._coeff*ty._coeff res = TensMul(res, [],[],[], is_canon_bp=True) - if delta in res._components: - return res.contract_delta(delta) + if contract_all == True and g in res._components: + return res.contract(g, is_metric, True) return res free2 = [] @@ -1182,10 +1191,13 @@ def contract_delta(self, delta): coeff = coeff*tg._coeff res = tensor_mul(*a) res = coeff*res - if delta in res._components: - return res.contract_delta(delta) + if contract_all == True and g in res._components: + return res.contract(g, is_metric, True) return res + def contract_delta(self, delta): + return self.contract(delta, False, True) + def contract_metric(self, g, contract_all=False): """ Raise or lower indices with the metric ``g`` @@ -1213,101 +1225,7 @@ def contract_metric(self, g, contract_all=False): if g.index_types[0].metric_antisym != 0: # TODO case of antisymmetric metric raise NotImplementedError - if not self._components: - return self - free_indices = [x[0] for x in self._free] - a = self.split() - typ = g.index_types[0] - # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim - for i, tx in enumerate(a): - if tx._components[0] == g: - free_indices_g = [x[0] for x in a[i]._free] - if len(free_indices_g) == 0: - a1 = a[:i] + a[i + 1:] - t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) - if contract_all == True and g in t._components: - return t.contract_metric(g, True) - return t - - # if all metric tensors have only free indices, there is no contraction - for i, tg in enumerate(a): - if tg._components[0] == g: - tg_free_indices = [x[0] for x in tg._free] - if all(indx in free_indices for indx in tg_free_indices): - continue - break - else: - return self - - # tg has one or two indices contracted with other tensors - # i position of tg in a - coeff = S.One - tg_free = tg._free - if tg_free[0][0] in free_indices or tg_free[1][0] in free_indices: - if tg_free[0][0] in free_indices: - ind_free = tg_free[0][0] - ind, ipos1, _ = tg_free[1] - else: - ind_free = tg_free[1][0] - ind, ipos1, _ = tg_free[0] - - ind1 = -ind - # search ind1 in self._dum - for j, tx in enumerate(a): - if ind1 in [x[0] for x in tx._free]: - break - free1 = [] - for indx, iposx, _ in tx._free: - if indx == ind1: - free1.append((ind_free, iposx, 0)) - else: - free1.append((indx, iposx, 0)) - t1 = TensMul(tx._coeff, tx._components, free1, tx._dum) - a[j] = t1 - a = a[:i] + a[i + 1:] - coeff = coeff*tg._coeff - res = tensor_mul(*a) - else: - # tg has two indices contracted with other tensors - ind1 = tg_free[0][0] - ind2 = tg_free[1][0] - ind1m = -ind1 - ind2m = -ind2 - for k, ty in enumerate(a): - if ind2m in [x[0] for x in ty._free]: - break - if ty._components == [g]: - ty_indices = [x[0] for x in ty._free] - if all(x in [ind1m, ind2m] for x in ty_indices): - if i < k: - a = a[:i] + a[i+1:k] + a[k+1:] - else: - a = a[:k] + a[k+1:i] + a[k+1:] - if a: - res = tensor_mul(*a) - res = (coeff*typ.dim*tg._coeff*ty._coeff)*res - else: - res = coeff*typ.dim*tg._coeff*ty._coeff - res = TensMul(res, [],[],[], is_canon_bp=True) - if contract_all == True and g in res._components: - return res.contract_metric(g, True) - return res - - free2 = [] - for indx, iposx, _ in ty._free: - if indx == ind2m: - free2.append((ind1, iposx, 0)) - else: - free2.append((indx, iposx, 0)) - t2 = TensMul(ty._coeff, ty._components, free2, ty._dum) - a[k] = t2 - a = a[:i] + a[i + 1:] - coeff = coeff*tg._coeff - res = tensor_mul(*a) - res = coeff*res - if contract_all == True and g in res._components: - return res.contract_metric(g, True) - return res + return self.contract(g, True, contract_all) def _pretty(self): diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 6bbbfb721c83..693a5c4c023f 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -1,4 +1,4 @@ -from sympy.core import S, Rational, Symbol +from sympy.core import S, Rational, Symbol, Basic from sympy.combinatorics import Permutation from sympy.combinatorics.tensor_can import (bsgs_direct_product, riemann_bsgs) from sympy.tensor.tensor import (TensorIndexType, tensor_indices, @@ -144,7 +144,7 @@ def test_no_metric_symmetry(): # no metric symmetry; A no symmetry # A^d1_d0 * A^d0_d1 # T_c = A^d0_d1 * A^d1_d0 - Lorentz = TensorIndexType('Lorentz', metric_antisym=None, dummy_fmt='L') + Lorentz = TensorIndexType('Lorentz', metric=None, dummy_fmt='L') d0, d1, d2, d3 = tensor_indices('d0 d1 d2 d3', Lorentz) nsym2 = TensorSymmetry(([], [Permutation(range(4))])) NS2 = TensorType([Lorentz]*2, nsym2) @@ -256,7 +256,7 @@ def test_canonicalize1(): # A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} - Spinor = TensorIndexType('Spinor', metric_antisym=1, dummy_fmt='S') + Spinor = TensorIndexType('Spinor', metric=1, dummy_fmt='S') a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) S3 = TensorType([Spinor]*3, sym3) @@ -271,7 +271,7 @@ def test_canonicalize1(): # no metric symmetry # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 - Mat = TensorIndexType('Mat', metric_antisym=None, dummy_fmt='M') + Mat = TensorIndexType('Mat', metric=None, dummy_fmt='M') a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) S3 = TensorType([Mat]*3, sym3) @@ -397,7 +397,7 @@ def test_riemann_products(): def test_canonicalize2(): D = Symbol('D') - Eucl = TensorIndexType('Eucl', metric_antisym=0, dim=D, dummy_fmt='E') + Eucl = TensorIndexType('Eucl', metric=0, dim=D, dummy_fmt='E') i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14 = \ tensor_indices('i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14', Eucl) sym3a = TensorSymmetry(get_symmetric_group_sgs(3, 1)) @@ -419,6 +419,29 @@ def test_canonicalize2(): t1 = t.canon_bp() assert t1 == 0 +class Metric(Basic): + def __new__(cls, name, antisym, **kwargs): + obj = Basic.__new__(cls, name, antisym, **kwargs) + obj.name = name + obj.antisym = antisym + return obj + +def test_TensorIndexType(): + D = Symbol('D') + G = Metric('g', False) + Lorentz = TensorIndexType('Lorentz', metric=G, dim=D, dummy_fmt='L') + m0, m1, m2, m3, m4 = tensor_indices('m0,m1,m2,m3,m4', Lorentz) + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Lorentz], sym1) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([Lorentz]*2, sym2) + g = Lorentz.metric + p = S1('p') + assert str(g) == 'g(Lorentz,Lorentz)' + t = g(m0, m1)*p(-m1) + t1 = t.contract_metric(g) + assert t1 == p(m0) + def test_get_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -732,3 +755,46 @@ def test_epsilon(): t = epsilon(c,a,d,b)*p(-a)*q(-b) + epsilon(a,b,c,d)*p(-b)*q(-a) t1 = t.canon_bp() assert t1 == -2*epsilon(c, d, a, b)*p(-a)*q(-b) + +def test_contract_delta1(): + # see Group Theory by Cvitanovic page 9 + n = Symbol('n') + Color = TensorIndexType('Color', metric=None, dim=n, dummy_fmt='C') + a, b, c, d, e, f = tensor_indices('a,b,c,d,e,f', Color) + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + S1 = TensorType([Color], sym1) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S2 = TensorType([Color]*2, sym2) + delta = Color.delta + + def idn(a, b, d, c): + assert a.is_contravariant and d.is_contravariant + assert not (b.is_contravariant or c.is_contravariant) + return delta(a, c)*delta(d, b) + + def T(a, b, d, c): + assert a.is_contravariant and d.is_contravariant + assert not (b.is_contravariant or c.is_contravariant) + return delta(a, b)*delta(d, c) + + def P1(a, b, c, d): + return idn(a,b,c,d) - 1/n*T(a,b,c,d) + + def P2(a, b, c, d): + return 1/n*T(a,b,c,d) + + t = P1(a, -b, e, -f)*P1(f, -e, d, -c) + t1 = t.contract_delta(delta) + assert t1 == P1(a, -b, d, -c) + + t = P2(a, -b, e, -f)*P2(f, -e, d, -c) + t1 = t.contract_delta(delta) + assert t1 == P2(a, -b, d, -c) + + t = P1(a, -b, e, -f)*P2(f, -e, d, -c) + t1 = t.contract_delta(delta) + assert t1 == 0 + + t = P1(a, -b, b, -a) + t1 = t.contract_delta(delta) + assert t1 == n**2 - 1 From 98743a3e1aeb4a44d4720fc26235480166a8a743 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 27 Dec 2012 09:35:50 +0100 Subject: [PATCH 18/47] fixed bug in TensMul.contract; added substitute_tensor --- sympy/tensor/tensor.py | 86 +++++++++++++++++++++++++++---- sympy/tensor/tests/test_tensor.py | 19 ++++++- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 7dec25fd4771..03c63f8765f3 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -352,7 +352,7 @@ def __call__(self, *indices): >>> A = S2('A') >>> t = A(a, -b) """ - assert self.rank == len(indices) + assert [indices[i].tensortype for i in range(len(indices))] == self.index_types components = [self] free, dum = TensMul.from_indices(*indices) free.sort(key=lambda x: x[0].name) @@ -667,6 +667,16 @@ def contract_metric(self, g, contract_all=False): t = TensAdd(*args) return t.canon_bp() + def substitute_tensor(self, t1, t2, subst_all=False): + args = self.args + args1 = [] + for x in args: + y = x.substitute_tensor(t1, t2, subst_all) + args1.append(y) + return TensAdd(*args1) + + + def _pretty(self): a = [] args = self.args @@ -1168,7 +1178,7 @@ def contract(self, g, is_metric, contract_all=False): if i < k: a = a[:i] + a[i+1:k] + a[k+1:] else: - a = a[:k] + a[k+1:i] + a[k+1:] + a = a[:k] + a[k+1:i] + a[i+1:] if a: res = tensor_mul(*a) res = (coeff*typ.dim*tg._coeff*ty._coeff)*res @@ -1178,14 +1188,31 @@ def contract(self, g, is_metric, contract_all=False): if contract_all == True and g in res._components: return res.contract(g, is_metric, True) return res - free2 = [] - for indx, iposx, _ in ty._free: - if indx == ind2m: - free2.append((ind1, iposx, 0)) - else: - free2.append((indx, iposx, 0)) - t2 = TensMul(ty._coeff, ty._components, free2, ty._dum) + # ty._free contains ind2m; replace ind2m with ind1 + # in the fee indices of ty, unless ty has ind1m as free index; + # in that case take away it from free and create a dummy entry + + ty_freeindices = [x[0] for x in ty._free] + if ind1m in ty_freeindices: + free2 = [(indx, iposx, cposx) for indx, iposx, cposx in ty._free if indx != ind1m and indx != ind2m] + dum2 = ty._dum[:] + for indx, iposx, _ in ty._free: + if indx == ind1m: + iposx1 = iposx + if indx == ind2m: + iposx2 = iposx + # ind1m is covariant + dum2.append((iposx2, iposx1, 0, 0)) + else: + free2 = [] + for indx, iposx, _ in ty._free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + else: + free2.append((indx, iposx, 0)) + dum2 = ty._dum + t2 = TensMul(ty._coeff, ty._components, free2, dum2) a[k] = t2 a = a[:i] + a[i + 1:] coeff = coeff*tg._coeff @@ -1227,6 +1254,47 @@ def contract_metric(self, g, contract_all=False): raise NotImplementedError return self.contract(g, True, contract_all) + def substitute_tensor(self, t1, t2, subst_all=False): + # FIXME fix the check + #assert sorted(t1._free) == sorted(t2._free) + components = self._components + assert len(t1._components) == 1 and t1._coeff == 1 + component1 = t1._components[0] + for i in range(len(components)): + if component1 == components[i]: + break + else: + return self + a = self.split() + if a[i]._dum != t1._dum: + return self + + ct = self._coeff if i == 0 else S.One + # sort free indices according to the position + free0 = sorted(a[i]._free, key=lambda x: x[1]) + free1 = sorted(t1._free, key=lambda x: x[1]) + index_tuples = [] + for j in range(len(free0)): + index_tuples.append((free1[j][0], free0[j][0])) + t2 = t2.substitute_indices(*index_tuples) + a1 = a[:i] + [t2] + a[i + 1:] + t = tensor_mul(*a1)*ct + return t + + def substitute_tensors(self, i, j, t): + """ + substitute tensors in positions ``i, j`` with tensor ``t`` + """ + a = self.split() + assert i < j + ct = self._coeff if i == 0 else S.One + a = self.split() + a1 = a[:i] + a[i + 1:j] + a[j + 1:] + [t] + t = tensor_mul(*a1)*ct + return t + + + def _pretty(self): if self._components == []: diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 693a5c4c023f..ced4fdd50594 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -273,7 +273,7 @@ def test_canonicalize1(): # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 Mat = TensorIndexType('Mat', metric=None, dummy_fmt='M') a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ - tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Mat) S3 = TensorType([Mat]*3, sym3) S2a = TensorType([Mat]*2, sym2a) A = S3('A', 1) @@ -549,6 +549,23 @@ def test_substitute_indices(): t1 = t.substitute_indices((-i, -j)) assert t1 == p(j) +def test_substitute_tensor(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + S1 = TensorType([Lorentz], sym1) + S2 = TensorType([Lorentz]*2, sym2) + p, q = S1('p,q') + A = S2('A') + t = 2*p(m0)*q(m1) + t1 = p(m1) + t2 = 3*A(m1, m2)*p(-m2) + t3 = t.substitute_tensor(t1, t2) + assert t3 == 6*A(m0, m2)*p(-m2)*q(m1) + t4 = t.substitute_tensor(q(m1), t2) + assert t4 == 6*p(m0)*A(m1, m2)*p(-m2) + def test_riemann_cyclic_replace(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') From a2f6e37f2d9d269bc6f09e5fea0d3038f8b3153c Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Fri, 28 Dec 2012 19:42:15 +0100 Subject: [PATCH 19/47] fixed bugs in canonical_free, get_indices and TensAdd.__new__; used tensorsymmetry --- sympy/combinatorics/tensor_can.py | 71 +++++++------- sympy/tensor/tensor.py | 154 ++++++++++++++++++++++-------- sympy/tensor/tests/test_tensor.py | 83 ++++++++-------- 3 files changed, 195 insertions(+), 113 deletions(-) diff --git a/sympy/combinatorics/tensor_can.py b/sympy/combinatorics/tensor_can.py index c122c79c2edd..792142eaaa48 100644 --- a/sympy/combinatorics/tensor_can.py +++ b/sympy/combinatorics/tensor_can.py @@ -530,14 +530,15 @@ def canonical_free(base, gens, g, num_free): """ canonicalization of a tensor with respect to free indices choosing the minimum with respect to lexicographical ordering + in the free indices - base, gens BSGS for slot permutation group - g permutation representing the tensor - num_free number of free indices + ``base``, ``gens`` BSGS for slot permutation group + ``g`` permutation representing the tensor + ``num_free`` number of free indices The indices must be ordered with first the free indices see explanation in double_coset_can_rep - The algorithm is given in [2] + The algorithm is a variation of the one given in [2]. Examples ======== @@ -552,20 +553,23 @@ def canonical_free(base, gens, g, num_free): [0, 3, 1, 2, 5, 4] Consider the product of Riemann tensors - `T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}` - The order of the indices is [a,b,d0,-d0,d1,-d1,d2,-d2] + ``T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}`` + The order of the indices is ``[a,b,d0,-d0,d1,-d1,d2,-d2]`` The permutation corresponding to the tensor is - g = [0,3,4,6,7,5,2,1,8,9] - Use the slot symmetries to get `T` is the form which is the minimum - in lexicographic order - `R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d1,d2}` corresponding to - `[0, 3, 4, 6, 1, 2, 5, 7, 8, 9]` + ``g = [0,3,4,6,7,5,2,1,8,9]`` + + In particular ``a`` is position ``0``, ``b`` is in position ``9``. + Use the slot symmetries to get `T` is a form which is the minimal + in lexicographic order in the free indices ``a`` and ``b``, e.g. + ``-R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d2,d1}`` corresponding to + ``[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]`` + >>> from sympy.combinatorics.tensor_can import riemann_bsgs, tensor_gens >>> base, gens = riemann_bsgs >>> size, sbase, sgens = tensor_gens(base, gens, [[],[]], 0) >>> g = Permutation([0,3,4,6,7,5,2,1,8,9]) >>> canonical_free(sbase, [Permutation(h) for h in sgens], g, 2) - [0, 3, 4, 6, 1, 2, 5, 7, 8, 9] + [0, 3, 4, 6, 1, 2, 7, 5, 9, 8] """ g = g.array_form size = len(g) @@ -573,27 +577,26 @@ def canonical_free(base, gens, g, num_free): return g[:] transversals = get_transversals(base, gens) - cosets = transversal2coset(size, base, transversals) - K = [h._array_form for h in gens] m = len(base) for x in sorted(g[:-2]): if x not in base: base.append(x) - lambd = g - K1 = [] - for i in range(m): + h = g + for i, transv in enumerate(transversals): b = base[i] - delta = [x[b] for x in cosets[b]] - delta1 = [lambd[x] for x in delta] - delta1min = min(delta1) - k = delta1.index(delta1min) - p = delta[k] - for omega in cosets[b]: - if omega[b] == p: - break - lambd = _af_rmul(lambd, omega) - K = [px for px in K if px[b] == b] - return lambd + h_i = [size]*num_free + # find the element s in transversals[i] such that + # _af_rmul(h, s) has its free elements with the lowest position in h + s = None + for sk in transv.values(): + h1 = _af_rmul(h, sk) + hi = [h1.index(ix) for ix in range(num_free)] + if hi < h_i: + h_i = hi + s = sk + if s: + h = _af_rmul(h, s) + return h def _get_map_slots(size, fixed_slots): @@ -923,10 +926,14 @@ def bsgs_direct_product(base1, gens1, base2, gens2, signed=True): return base, [_af_new(h) for h in gens] -def get_symmetric_group_sgs(n, sym=0): +def get_symmetric_group_sgs(n, antisym=False): """ - Return base, gens of the minimal BSGS for (anti)symmetric group - with n elements + Return base, gens of the minimal BSGS for (anti)symmetric tensor + + ``n`` rank of the tensor + + ``antisym = False`` symmetric tensor + ``antisym = True`` antisymmetric tensor Examples ======== @@ -940,7 +947,7 @@ def get_symmetric_group_sgs(n, sym=0): if n == 1: return [], [_af_new(range(3))] gens = [Permutation(n - 1)(i, i + 1)._array_form for i in range(n - 1)] - if sym == 0: + if antisym == 0: gens = [x + [n, n + 1] for x in gens] else: gens = [x + [n + 1, n] for x in gens] diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 03c63f8765f3..40a942104a3e 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -190,7 +190,6 @@ class TensorSymmetry(Basic): >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) >>> S2 = TensorType([Lorentz]*2, sym2) >>> V = S2('V') @@ -204,6 +203,72 @@ def __new__(cls, bsgs, **kw_args): generators = property(lambda self: self.args[1]) rank = property(lambda self: self.args[1][0].size) +def tensorsymmetry(*args): + """ + return a ``TensorSymmetry`` object + + One can represent a tensor with any slot symmetry group using a BSGS + ``args`` can be a BSGS + ``args[0]`` base + ``args[1]`` sgs + + Usually tensors are in (direct products of) irreducible representations + of the symmetric group; + ``args`` can be a list of lists representing Young tableaux + ``[[1]]`` vector + ``[[1]*n]`` symmetric tensor of rank ``n`` + ``[[n]`` antisymmetric tensor of rank ``n`` + ``[[2, 2]]`` slot symmetry of the Riemann tensor + ``[[1],[1]]`` vector*vector + + TODO implement this with arbitrary Young tableaux + + Examples + ======== + + Symmetric tensor using a BSBS + + Symmetric tensor using a Young tableau + + >>> from sympy.tensor.tensor import TensorIndexType, TensorType, tensorsymmetry + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> sym2 = tensorsymmetry([1, 1]) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> V = S2('V') + + Symmetric tensor using a BSGS + >>> from sympy.tensor.tensor import TensorSymmetry, get_symmetric_group_sgs + >>> sym2 = tensorsymmetry(*get_symmetric_group_sgs(2)) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> V = S2('V') + """ + from sympy.combinatorics import Permutation + def tableau2bsgs(a): + if len(a) == 1: + # antisymmetric vector + n = a[0] + bsgs = get_symmetric_group_sgs(n, 1) + else: + if all(x == 1 for x in a): + # symmetric vector + n = len(a) + bsgs = get_symmetric_group_sgs(n) + elif a == [2, 2]: + bsgs = riemann_bsgs + else: + raise NotImplementedError + return bsgs + + + if not args: + return TensorSymmetry([[], [Permutation(2)]]) + if len(args) == 2 and isinstance(args[1][0], Permutation): + return TensorSymmetry(args) + base, sgs = tableau2bsgs(args[0]) + for a in args[1:]: + basex, sgsx = tableau2bsgs(a) + base, sgs = bsgs_direct_product(base, sgs, basex, sgsx) + return TensorSymmetry((base, sgs)) class TensorType(Basic): @@ -215,10 +280,9 @@ class TensorType(Basic): Define a symmetric tensor - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) - >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> sym2 = tensorsymmetry([1, 1]) >>> S2 = TensorType([Lorentz]*2, sym2) >>> V = S2('V') """ @@ -254,10 +318,10 @@ def __call__(self, s, anticommuting=False): Define symmetric tensors ``V``, ``W`` and ``G``, respectively commuting, anticommuting and with no commutation symmetry - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs, canon_bp + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType, canon_bp >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) - >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> sym2 = tensorsymmetry([1]*2) >>> S2 = TensorType([Lorentz]*2, sym2) >>> V = S2('V') >>> W = S2('W', 1) @@ -295,10 +359,9 @@ def __new__(cls, name, typ, anticommuting, **kw_args): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - >>> a, b = tensor_indices('a,b', Lorentz) - >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> sym2 = tensorsymmetry([1]*2) >>> S2 = TensorType([Lorentz]*2, sym2) >>> A = S2('A') """ @@ -344,10 +407,10 @@ def __call__(self, *indices): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) - >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> sym2 = tensorsymmetry([1]*2) >>> S2 = TensorType([Lorentz]*2, sym2) >>> A = S2('A') >>> t = A(a, -b) @@ -451,10 +514,10 @@ def substitute_indices(self, *index_tuples): ======== >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) - >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> sym2 = tensorsymmetry([1]*2) >>> S2 = TensorType([Lorentz]*2, sym2) >>> A, B = S2('A,B') >>> t = A(i, k)*B(-k, -j); t @@ -577,10 +640,10 @@ class TensAdd(TensExpr): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, TensorType, get_symmetric_group_sgs, tensor_indices, TensAdd + >>> from sympy.tensor.tensor import TensorIndexType, tensorsymmetry, TensorType, tensor_indices, TensAdd >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) - >>> sym = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> sym = tensorsymmetry([1]) >>> S1 = TensorType([Lorentz], sym) >>> p, q = S1('p,q') >>> t1 = p(a) @@ -608,11 +671,12 @@ def __new__(cls, *args, **kw_args): a = _tensAdd_collect_terms(args) if not a: return S.Zero - a = [canon_bp(x) for x in a] a = [x for x in a if x] if not a: return S.Zero + if len(a) == 1: + return a[0] obj._args = tuple(a) return obj @@ -675,8 +739,6 @@ def substitute_tensor(self, t1, t2, subst_all=False): args1.append(y) return TensAdd(*args1) - - def _pretty(self): a = [] args = self.args @@ -728,7 +790,8 @@ def __eq__(self, other): return self._coeff == 0 other = sympify(other) if not is_Tensor(other): - return False + assert not self._components + return self._coeff == other res = self - other return res == 0 @@ -749,7 +812,7 @@ def from_indices(*indices): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, TensMul, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensMul >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) >>> TensMul.from_indices(m0, m1, -m1, m3) @@ -805,10 +868,10 @@ def get_indices(self): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> sym1 = tensorsymmetry([1]) >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric >>> p, q = S1('p,q') @@ -825,7 +888,12 @@ def get_indices(self): vpos.append(pos) pos += t.rank cdt = defaultdict(int) + # if the free indices have names with dummy_fmt, start with an + # index higher than those for the dummy indices + # to avoid name collisions for indx, ipos, cpos in self._free: + if indx.name.split('_')[0] == indx.tensortype.dummy_fmt[:-3]: + cdt[indx.tensortype] = max(cdt[indx.tensortype], int(indx.name.split('_')[1]) + 1) start = vpos[cpos] indices[start + ipos] = indx for ipos1, ipos2, cpos1, cpos2 in self._dum: @@ -851,10 +919,10 @@ def split(self): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, get_symmetric_group_sgs + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) - >>> sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> sym2 = tensorsymmetry([1]*2) >>> S2 = TensorType([Lorentz]*2, sym2) >>> A, B = S2('A,B') >>> t = A(a,b)*B(-b,c) @@ -892,10 +960,13 @@ def canon_args(self): for t in self._components: vpos.append(pos) pos += t.rank + # ordered indices: first the free indices, ordered by types + # then the dummy indices, ordered by types and contravariant before + # covariant + # g[position in tensor] = position in ordered indices for i, (indx, ipos, cpos) in enumerate(self._free): pos = vpos[cpos] + ipos g[pos] = i - pos = len(self._free) j = len(self._free) dummies = [] @@ -943,10 +1014,10 @@ def __mul__(self, other): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> sym1 = tensorsymmetry([1]) >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric >>> p, q = S1('p,q') @@ -1081,10 +1152,10 @@ def canon_bp(self): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + >>> sym2a = tensorsymmetry([2]) >>> S2 = TensorType([Lorentz]*2, sym2a) >>> A = S2('A') >>> t = A(m0,-m1)*A(m1,-m0) @@ -1106,7 +1177,10 @@ def canon_bp(self): return S.Zero return t.perm2tensor(can, True) - def contract(self, g, is_metric, contract_all=False): + def _contract(self, g, is_metric, contract_all=False): + """ + helper method for ``contract_metric`` and ``contract_delta`` + """ if not self._components: return self free_indices = [x[0] for x in self._free] @@ -1120,7 +1194,7 @@ def contract(self, g, is_metric, contract_all=False): a1 = a[:i] + a[i + 1:] t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) if contract_all == True and g in t._components: - return t.contract(g, is_metric, True) + return t._contract(g, is_metric, True) return t # if all metric tensors have only free indices, there is no contraction @@ -1186,7 +1260,7 @@ def contract(self, g, is_metric, contract_all=False): res = coeff*typ.dim*tg._coeff*ty._coeff res = TensMul(res, [],[],[], is_canon_bp=True) if contract_all == True and g in res._components: - return res.contract(g, is_metric, True) + return res._contract(g, is_metric, True) return res free2 = [] # ty._free contains ind2m; replace ind2m with ind1 @@ -1219,11 +1293,11 @@ def contract(self, g, is_metric, contract_all=False): res = tensor_mul(*a) res = coeff*res if contract_all == True and g in res._components: - return res.contract(g, is_metric, True) + return res._contract(g, is_metric, True) return res def contract_delta(self, delta): - return self.contract(delta, False, True) + return self._contract(delta, False, True) def contract_metric(self, g, contract_all=False): """ @@ -1236,10 +1310,10 @@ def contract_metric(self, g, contract_all=False): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + >>> sym1 = tensorsymmetry([1]) >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric >>> p, q = S1('p,q') @@ -1252,7 +1326,7 @@ def contract_metric(self, g, contract_all=False): if g.index_types[0].metric_antisym != 0: # TODO case of antisymmetric metric raise NotImplementedError - return self.contract(g, True, contract_all) + return self._contract(g, True, contract_all) def substitute_tensor(self, t1, t2, subst_all=False): # FIXME fix the check @@ -1294,8 +1368,6 @@ def substitute_tensors(self, i, j, t): return t - - def _pretty(self): if self._components == []: return str(self._coeff) @@ -1386,10 +1458,10 @@ def riemann_cyclic(t2): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType, riemann_cyclic, riemann_bsgs + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType, riemann_cyclic >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) - >>> symr = TensorSymmetry(riemann_bsgs) + >>> symr = tensorsymmetry([2, 2]) >>> R4 = TensorType([Lorentz]*4, symr) >>> R = R4('R') >>> t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index ced4fdd50594..4498e1719b74 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -4,7 +4,7 @@ from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, - tensorlist_contract_metric, TensMul) + tensorlist_contract_metric, TensMul, tensorsymmetry) from sympy.utilities.pytest import raises #################### Tests from tensor_can.py ####################### @@ -13,7 +13,7 @@ def test_canonicalize_no_slot_sym(): # A_d0 * B^d0; T_c = A^d0*B_d0 Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, d0, d1 = tensor_indices('a,b,d0,d1', Lorentz) - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) S1 = TensorType([Lorentz], sym1) A, B = S1('A,B') t = A(-d0)*B(d0) @@ -31,7 +31,7 @@ def test_canonicalize_no_slot_sym(): # A symmetric # A^{b}_{d0}*A^{d0, a}; T_c = A^{a d0}*A{b}_{d0} - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2 = tensorsymmetry([1]*2) S2 = TensorType([Lorentz]*2, sym2) A = S2('A') t = A(b, -d0)*A(d0, a) @@ -48,7 +48,7 @@ def test_canonicalize_no_slot_sym(): # A without symmetry # A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5] # T_c = A^{d0 d1}*B_d1*C_d0; can = [0,2,3,1,4,5] - nsym2 = TensorSymmetry(([], [Permutation(range(4))])) + nsym2 = tensorsymmetry([1],[1]) NS2 = TensorType([Lorentz]*2, nsym2) A = NS2('A') B, C = S1('B, C') @@ -96,9 +96,9 @@ def test_canonicalize_no_slot_sym(): def test_canonicalize_no_dummies(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a, b, c, d', Lorentz) - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) - sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + sym1 = tensorsymmetry([1]) + sym2 = tensorsymmetry([1]*2) + sym2a = tensorsymmetry([2]) # A commuting # A^c A^b A^a @@ -146,7 +146,7 @@ def test_no_metric_symmetry(): # T_c = A^d0_d1 * A^d1_d0 Lorentz = TensorIndexType('Lorentz', metric=None, dummy_fmt='L') d0, d1, d2, d3 = tensor_indices('d0 d1 d2 d3', Lorentz) - nsym2 = TensorSymmetry(([], [Permutation(range(4))])) + nsym2 = tensorsymmetry([1], [1]) NS2 = TensorType([Lorentz]*2, nsym2) A = NS2('A') t = A(d1, -d0)*A(d0, -d1) @@ -169,12 +169,12 @@ def test_canonicalize1(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Lorentz) - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) base3, gens3 = get_symmetric_group_sgs(3) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) - sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) - sym3 = TensorSymmetry(get_symmetric_group_sgs(3)) - sym3a = TensorSymmetry(get_symmetric_group_sgs(3, 1)) + sym2 = tensorsymmetry([1]*2) + sym2a = tensorsymmetry([2]) + sym3 = tensorsymmetry([1]*3) + sym3a = tensorsymmetry([3]) # A_d0*A^d0; ord = [d0,-d0] # T_c = A^d0*A_d0 @@ -310,10 +310,9 @@ def test_canonicalize1(): Flavor = TensorIndexType('Flavor', dummy_fmt='F') a, b, c, d, e, ff = tensor_indices('a,b,c,d,e,f', Flavor) mu, nu = tensor_indices('mu,nu', Lorentz) - sym_f = TensorSymmetry(bsgs_direct_product(sym1.base, sym1.generators, - sym2a.base, sym2a.generators)) + sym_f = tensorsymmetry([1], [2]) S_f = TensorType([Flavor]*3, sym_f) - sym_A = TensorSymmetry(bsgs_direct_product(sym1.base, sym1.generators, sym1.base, sym1.generators)) + sym_A = tensorsymmetry([1], [1]) S_A = TensorType([Lorentz, Flavor], sym_A) f = S_f('f') A = S_A('A') @@ -358,9 +357,9 @@ def test_riemann_products(): symr = TensorSymmetry(riemann_bsgs) R4 = TensorType([Lorentz]*4, symr) R = R4('R') - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) - sym2a = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + sym1 = tensorsymmetry([1]) + sym2 = tensorsymmetry([1]*2) + sym2a = tensorsymmetry([2]) # R^{a b d0}_d0 = 0 t = R(a, b, d0, -d0) tc = t.canon_bp() @@ -400,7 +399,7 @@ def test_canonicalize2(): Eucl = TensorIndexType('Eucl', metric=0, dim=D, dummy_fmt='E') i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14 = \ tensor_indices('i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14', Eucl) - sym3a = TensorSymmetry(get_symmetric_group_sgs(3, 1)) + sym3a = tensorsymmetry([3]) S3a = TensorType([Eucl]*3, sym3a) A = S3a('A') @@ -431,9 +430,9 @@ def test_TensorIndexType(): G = Metric('g', False) Lorentz = TensorIndexType('Lorentz', metric=G, dim=D, dummy_fmt='L') m0, m1, m2, m3, m4 = tensor_indices('m0,m1,m2,m3,m4', Lorentz) - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) S1 = TensorType([Lorentz], sym1) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2 = tensorsymmetry([1]*2) S2 = TensorType([Lorentz]*2, sym2) g = Lorentz.metric p = S1('p') @@ -447,7 +446,7 @@ def test_get_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) assert a != -a - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2 = tensorsymmetry([1]*2) S2 = TensorType([Lorentz]*2, sym2) A, B = S2('A B') assert A != B @@ -466,8 +465,8 @@ def test_add1(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a,b,d0,d1,i,j,k = tensor_indices('a,b,d0,d1,i,j,k', Lorentz) # A, B symmetric - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym1 = tensorsymmetry([1]) + sym2 = tensorsymmetry([1]*2) S2 = TensorType([Lorentz]*2, sym2) A, B = S2('A,B') t1 = A(b,-d0)*B(d0,a) @@ -509,8 +508,8 @@ def test_add1(): def test_add2(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m, n, p, q = tensor_indices('m,n,p,q', Lorentz) - symr = TensorSymmetry(riemann_bsgs) - sym3a = TensorSymmetry(get_symmetric_group_sgs(3, 1)) + symr = tensorsymmetry([2, 2]) + sym3a = tensorsymmetry([3]) R4 = TensorType([Lorentz]*4, symr) S3a = TensorType([Lorentz]*3, sym3a) R = R4('R') @@ -526,7 +525,7 @@ def test_add2(): def test_substitute_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') i, j, k, l, m, n, p, q = tensor_indices('i,j,k,l,m,n,p,q', Lorentz) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2 = tensorsymmetry([1]*2) S2 = TensorType([Lorentz]*2, sym2) A, B = S2('A,B') t = A(i, k)*B(-k, -j) @@ -534,7 +533,7 @@ def test_substitute_indices(): t1a = A(j, l)*B(-l, -k) assert t1 == t1a - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) S1 = TensorType([Lorentz], sym1) p = S1('p') t = p(i) @@ -552,8 +551,8 @@ def test_substitute_indices(): def test_substitute_tensor(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym1 = tensorsymmetry([1]) + sym2 = tensorsymmetry([1]*2) S1 = TensorType([Lorentz], sym1) S2 = TensorType([Lorentz]*2, sym2) p, q = S1('p,q') @@ -566,11 +565,10 @@ def test_substitute_tensor(): t4 = t.substitute_tensor(q(m1), t2) assert t4 == 6*p(m0)*A(m1, m2)*p(-m2) - def test_riemann_cyclic_replace(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m0,m1,m2,m3 = tensor_indices('m0,m1,m2,m3', Lorentz) - symr = TensorSymmetry(riemann_bsgs) + symr = tensorsymmetry([2, 2]) R4 = TensorType([Lorentz]*4, symr) R = R4('R') t = R(m0,m2,m1,m3) @@ -581,7 +579,7 @@ def test_riemann_cyclic_replace(): def test_riemann_cyclic(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') i, j, k, l, m, n, p, q = tensor_indices('i,j,k,l,m,n,p,q', Lorentz) - symr = TensorSymmetry(riemann_bsgs) + symr = tensorsymmetry([2, 2]) R4 = TensorType([Lorentz]*4, symr) R = R4('R') t = R(i,j,k,l) + R(i,l,j,k) + R(i,k,l,j) - \ @@ -600,7 +598,7 @@ def test_riemann_cyclic(): def test_div(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m0,m1,m2,m3 = tensor_indices('m0,m1,m2,m3', Lorentz) - symr = TensorSymmetry(riemann_bsgs) + symr = tensorsymmetry([2, 2]) R4 = TensorType([Lorentz]*4, symr) R = R4('R') t = R(m0,m1,-m1,m3) @@ -617,7 +615,7 @@ def test_metric_contract1(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2 = tensorsymmetry([1]*2) S2 = TensorType([Lorentz]*2, sym2) g = Lorentz.metric A, B = S2('A,B') @@ -647,12 +645,17 @@ def test_metric_contract1(): t2 = t2.contract_metric(g) assert t2 == A(a,b)*B(-b,-a) + t1 = A(a,b)*g(-a,-b) + t2 = t1.contract_metric(g) + assert t2 == A(a, -a) + assert not t2._free + def test_metric_contract1(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) g = Lorentz.metric - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) S1 = TensorType([Lorentz], sym1) p, q = S1('p,q') @@ -737,7 +740,7 @@ def test_epsilon(): a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) g = Lorentz.metric epsilon = Lorentz.epsilon - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) S1 = TensorType([Lorentz], sym1) p, q, r, s = S1('p,q,r,s') @@ -778,9 +781,9 @@ def test_contract_delta1(): n = Symbol('n') Color = TensorIndexType('Color', metric=None, dim=n, dummy_fmt='C') a, b, c, d, e, f = tensor_indices('a,b,c,d,e,f', Color) - sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) + sym1 = tensorsymmetry([1]) S1 = TensorType([Color], sym1) - sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + sym2 = tensorsymmetry([2]) S2 = TensorType([Color]*2, sym2) delta = Color.delta From c5e2143946c069355e456d57b8b26580c320731d Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 1 Jan 2013 12:49:30 +0100 Subject: [PATCH 20/47] Fixed bugs in TensAdd.__new__; added expand_coeff, _as_coeff_mul --- sympy/combinatorics/tests/test_tensor_can.py | 12 +++++ sympy/tensor/tensor.py | 55 +++++++++++++++++--- sympy/tensor/tests/test_tensor.py | 11 +++- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/sympy/combinatorics/tests/test_tensor_can.py b/sympy/combinatorics/tests/test_tensor_can.py index ab014a38bddc..8c2a4d5fc557 100644 --- a/sympy/combinatorics/tests/test_tensor_can.py +++ b/sympy/combinatorics/tests/test_tensor_can.py @@ -241,6 +241,18 @@ def test_no_metric_symmetry(): can = canonicalize(g, range(16), None, [[], [Permutation(range(4))], 8, 0]) assert can == [0,3,2,5,4,7,6,1,8,11,10,13,12,15,14,9,16,17] +def test_canonical_free(): + # t = A^{d0 a1}*A_d0^a0 + # ord = [a0,a1,d0,-d0]; g = [2,1,3,0,4,5]; dummies = [[2,3]] + # t_c = A_d0^a0*A^{d0 a1} + # can = [3,0, 2,1, 4,5] + base = [0] + gens = [Permutation(5)(0,2)(1,3)] + g = Permutation([2,1,3,0,4,5]) + num_free = 2 + dummies = [[2,3]] + can = canonicalize(g, dummies, [None], ([], [Permutation(3)], 2, 0)) + assert can == [3,0, 2,1, 4,5] def test_canonicalize1(): base1, gens1 = get_symmetric_group_sgs(1) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 40a942104a3e..277e0bd24180 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -548,6 +548,7 @@ def substitute_indices(self, *index_tuples): args1.append(y) return TensAdd(*args1) + def _tensAdd_collect_terms(args): """ collect TensMul terms differing at most by their coefficient @@ -668,9 +669,11 @@ def __new__(cls, *args, **kw_args): obj._args = tuple(args) return obj args.sort(key=lambda x: (x._components, x._free, x._dum)) - a = _tensAdd_collect_terms(args) - if not a: + args = [x.canon_bp() for x in args if x] + args = [x for x in args if x] + if not args: return S.Zero + a = _tensAdd_collect_terms(args) a = [canon_bp(x) for x in a] a = [x for x in a if x] if not a: @@ -714,7 +717,7 @@ def contract_delta(self, delta): if len(args) == 1: return args[0] t = TensAdd(*args) - return t.canon_bp() + return canon_bp(t) def contract_metric(self, g, contract_all=False): """ @@ -739,6 +742,10 @@ def substitute_tensor(self, t1, t2, subst_all=False): args1.append(y) return TensAdd(*args1) + def expand_coeff(self): + args = [_tensor_expand_coeff(x) for x in self.args] + return TensAdd(args) + def _pretty(self): a = [] args = self.args @@ -836,6 +843,7 @@ def from_indices(*indices): # check consistency and update free if is_contr: if contr: + print 'ERR indices=', indices raise ValueError('two equal contravariant indices in slots %d and %d' %(pos, i)) else: free[pos] = False @@ -845,6 +853,7 @@ def from_indices(*indices): free[pos] = False free[i] = False else: + print 'ERR indices=', indices raise ValueError('two equal covariant indices in slots %d and %d' %(pos, i)) if contr: dum.append((i, pos, 0, 0)) @@ -934,6 +943,8 @@ def split(self): indices = self.get_indices() pos = 0 components = self._components + if not components: + return [TensMul(self._coeff, [], [], [])] res = [] for t in self._components: t1 = t(*indices[pos:pos + t.rank]) @@ -1329,6 +1340,9 @@ def contract_metric(self, g, contract_all=False): return self._contract(g, True, contract_all) def substitute_tensor(self, t1, t2, subst_all=False): + """ + Return tensor obtained substituting ``t1`` with ``t2`` in ``self``. + """ # FIXME fix the check #assert sorted(t1._free) == sorted(t2._free) components = self._components @@ -1350,23 +1364,42 @@ def substitute_tensor(self, t1, t2, subst_all=False): index_tuples = [] for j in range(len(free0)): index_tuples.append((free1[j][0], free0[j][0])) - t2 = t2.substitute_indices(*index_tuples) - a1 = a[:i] + [t2] + a[i + 1:] + t2s = t2.substitute_indices(*index_tuples) + a1 = a[:i] + [t2s] + a[i + 1:] t = tensor_mul(*a1)*ct + if subst_all: + return t.substitute_tensor(t1, t2, subst_all) return t - def substitute_tensors(self, i, j, t): + def substitute_tensors(self, i, j, t, a=None): """ substitute tensors in positions ``i, j`` with tensor ``t`` """ - a = self.split() + if not a: + a = self.split() assert i < j ct = self._coeff if i == 0 else S.One - a = self.split() + #a = self.split() a1 = a[:i] + a[i + 1:j] + a[j + 1:] + [t] t = tensor_mul(*a1)*ct return t + def _as_coeff_mul(self): + a = self.split() + coeff = a[0]._coeff() + t0 = TensMul(S.One, a[0]._components, a[0]._free, a[0]._dum) + a[0] = t0 + t1 = tensor_mul(*a) + return coeff, t1 + + def expand_coeff(self): + a = self.split() + self._coeff = self._coeff.expand() + return self + + def expand_coeff(self): + coeff, t = self._as_coeff_mul() + return coeff.expand()*t def _pretty(self): if self._components == []: @@ -1411,6 +1444,12 @@ def tensor_mul(*a): t = t*tx return t +def _tensor_expand_coeff(p): + if not is_Tensor(p): + return p + return p.expand_coeff() + + def tensorlist_contract_metric(a, tg): """ contract `tg` with a tensor in the list `a = t.split()` diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 4498e1719b74..12122e83b320 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -474,7 +474,7 @@ def test_add1(): t2 = A(b,-d0)*t2a assert str(t2) == 'A(a, L_0)*A(b, -L_0) + A(b, L_0)*B(a, -L_0)' t2b = t2 + t1 - assert str(t2b) == 'A(a, L_0)*A(b, -L_0) + A(b, L_0)*B(a, -L_0) + A(b, L_0)*B(a, -L_0)' + assert str(t2b) == '2*A(b, L_0)*B(a, -L_0) + A(a, L_0)*A(b, -L_0)' S1 = TensorType([Lorentz], sym1) p, q, r = S1('p,q,r') t = q(d0)*2 @@ -653,10 +653,12 @@ def test_metric_contract1(): def test_metric_contract1(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') - a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) + a, b, c, d, e, L_0 = tensor_indices('a,b,c,d,e,L_0', Lorentz) g = Lorentz.metric sym1 = tensorsymmetry([1]) + sym2 = tensorsymmetry([1]*2) S1 = TensorType([Lorentz], sym1) + S2 = TensorType([Lorentz]*2, sym2) p, q = S1('p,q') t1 = g(a,b)*p(c)*p(-c) @@ -735,6 +737,11 @@ def test_metric_contract1(): v1 = tensorlist_contract_metric(v, g(d, e)) assert v1 == [p(a), q(b), p(c), g(d, e)] + A = S2('A') + t = A(a, b)*p(L_0)*g(-a, -b) + t1 = t.contract_metric(g) + assert str(t1) == 'A(L_1, -L_1)*p(L_0)' or str(t1) == 'A(-L_1, L_1)*p(L_0)' + def test_epsilon(): Lorentz = TensorIndexType('Lorentz', dim=4, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) From e62883b3cf6dc614ee330fa2d8c6137cfff21b8f Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 1 Jan 2013 12:58:22 +0100 Subject: [PATCH 21/47] taken away useless code in TensAdd.__new__ --- sympy/tensor/tensor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 277e0bd24180..9a0a9b361d62 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -674,8 +674,6 @@ def __new__(cls, *args, **kw_args): if not args: return S.Zero a = _tensAdd_collect_terms(args) - a = [canon_bp(x) for x in a] - a = [x for x in a if x] if not a: return S.Zero if len(a) == 1: From 2a697ae594f6ef0b35bc4017159d5f6c3527ae78 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 1 Jan 2013 20:04:36 +0100 Subject: [PATCH 22/47] started group_factors.py --- sympy/tensor/group_factors.py | 284 +++++++++++++++++++++++ sympy/tensor/tests/test_group_factors.py | 53 +++++ 2 files changed, 337 insertions(+) create mode 100644 sympy/tensor/group_factors.py create mode 100644 sympy/tensor/tests/test_group_factors.py diff --git a/sympy/tensor/group_factors.py b/sympy/tensor/group_factors.py new file mode 100644 index 000000000000..28473c868cce --- /dev/null +++ b/sympy/tensor/group_factors.py @@ -0,0 +1,284 @@ +from sympy import I, S +from sympy.tensor.tensor import (TensorIndexType, tensor_indices, \ +TensorSymmetry, get_symmetric_group_sgs, TensorType, TensMul, tensorlist_contract_metric, tensor_mul, TensAdd, TensExpr, tensorsymmetry) + + + +def match_CijCij(t, th): + """ + match for two tensors components ``th`` with two or more indices contracted + """ + components = t._components + rdum = [tuple(sorted([cpos1, cpos2])) for ipos1, ipos2, cpos1, cpos2 \ + in t._dum if components[cpos1] == th and components[cpos2] == th] + rdum.sort() + vcount = [rdum.count(x) for x in rdum] + repeated = {} + for i in range(len(rdum)): + if vcount[i] > 1 and rdum[i] not in repeated: + repeated[rdum[i]] = vcount[i] + if not repeated: + return None + repeated = repeated.items() + res = max(repeated, key=lambda x: x[1]) + return res + +def match_C_triangle(t, th): + """ + match for three tensor components ``th`` with triangle of contractions + """ + components = t._components + rdum = [sorted([cpos1, cpos2]) for ipos1, ipos2, cpos1, cpos2 in \ + t._dum if components[cpos1] == th and components[cpos2] == th] + rdum.sort() + for cpos1, cpos2 in rdum: + for i in range(cpos1 + 1, len(rdum)): + if [cpos1, i] in rdum: + if [cpos2, i] in rdum: + return cpos1, cpos2, i + elif [i, cpos2] in rdum: + return cpos1, i, cpos2 + +def match_C_square(t, th): + """ + match for four tensor components ``th`` with square of contractions + + It is assumed that there are not smaller cycles + """ + components = t._components + rdum = [sorted([cpos1, cpos2]) for ipos1, ipos2, cpos1, cpos2 in \ + t._dum if components[cpos1] == th and components[cpos2] == th] + rdum.sort() + for i in range(len(rdum)): + cpos1, cpos2 = rdum[i] + for j in range(i + 1, len(rdum)): + cpos3, cpos4 = rdum[j] + if cpos3 in rdum[i] or cpos4 in rdum[i]: + continue + if [cpos1, cpos3] in rdum: + if [cpos2, cpos4] in rdum or [cpos4, cpos2] in rdum: + return cpos1, cpos2, cpos4, cpos3 + if [cpos1, cpos4] in rdum: + if [cpos2, cpos3] in rdum or [cpos3, cpos2] in rdum: + return cpos1, cpos2, cpos3, cpos4 + + +class SuNGroupFactors(object): + """ + SU(N) + Lie algebra ``[T(i), T(j)] = I*C(i,j, -k)*T(k)`` + + ``tr(T(i)*T(j)) = g(i, j)`` + + ``g(i, j)`` is the metric in the adjoint representation space; + it is proportional to the identity matrix due to Shur lemma. + + ``T(i, -a, b)`` is the generator in the defining representation ``N`` + For ``N > 2`` there is no metric in the defining representation. + + Define ``theta(i_1, ..., i_n) = tr(T(i_1)*...*T^(i_n)) + + ``C_(i j k) = -I*theta(i, j, k) + I*theta(j, i, k)`` + + ``T(i,-d,a)*T(-i,-b,c) =`` + ``delta(-b,a)*delta(-d,c) - 1/N*delta(-d,a)*delta(-b,c)`` + + Using this relation one obtains easily relations among contracted + ``theta`` tensors. + """ + def __init__(self, N): + self.N = N + self.DSuN = TensorIndexType('DSuN', metric=None, dim=N, dummy_fmt='D') + self.ASuN = TensorIndexType('ASuN', metric=0, dim = N**2-1, dummy_fmt='A') + self.delta = self.DSuN.delta + self.g = self.ASuN.metric + symT = tensorsymmetry(*[[1]]*3) + ST = TensorType([self.ASuN, self.DSuN, self.DSuN], symT) + self.T = ST('T') + symA = tensorsymmetry([3]) + SC = TensorType([self.ASuN]*3, symA) + self.C = SC('C') + self._theta = [None]*3 + [TensorType([self.ASuN]*i, \ + tensorsymmetry(*[[1]]*i))('theta') for i in range(3, 6)] + i0, i1, i2 = tensor_indices('i0,i1,i2', self.ASuN) + self.tC = self.C(i0,i1,i2) + self.tCs = -I*self.theta(i0, i1, i2) + I*self.theta(i1, i0, i2) + + def theta(self, *indices): + n = len(indices) + if n == 1: + return S.Zero + if n == 2: + return self.g(*indices) + try: + return self._theta[n](*indices) + except IndexError: + for i in range(len(self._theta), n + 1): + self._theta.append(TensorType([self.ASuN]*i, tensorsymmetry(*[[1]]*i))('theta')) + return self._theta[n](*indices) + + def match_thetaii(self, t): + """ + match a ``theta`` with two indices contracted + + Returns the slot position of the contracted indices and the position + of the ``theta``. + """ + components = t._components + dum = t._dum + for ipos1, ipos2, cpos1, cpos2 in dum: + if cpos1 == cpos2 and \ + components[cpos1].name == 'theta' and \ + components[cpos1].types == [self.ASuN]: + return ipos1, ipos2, cpos1 + + def match_thetaithetai(self, t): + """ + match for two component tensors ``theta`` with + an index contracted with each other + + Return the positions of the contracted indices and the positions + of the two tensors. + """ + components = t._components + dum = t._dum + for ipos1, ipos2, cpos1, cpos2 in dum: + if cpos1 != cpos2 and \ + components[cpos1].name == 'theta' and \ + components[cpos2].name == 'theta' and \ + components[cpos1].types == components[cpos1].types == [self.ASuN]: + return ipos1, ipos2, cpos1, cpos2 + + + def rule_C_triangle(self, t): + """ + evaluate a triangle of C's + """ + if t.is_TensAdd: + args = t.args + args = [self.rule_C_triangle(x) for x in args] + return TensAdd(*args) + r = match_C_triangle(t, self.C) + if not r: + return t + return t + + def rule_C_square(self, t): + """ + evaluate a square of C's + """ + if t.is_TensAdd: + args = t.args + args = [self.rule_C_square(x) for x in args] + return TensAdd(*args) + r = match_C_square(t, self.C) + if not r: + return t + i0, i1, i2, i3 = sorted(r) + a = t.split() + a1 = a[:i0] + a[i0 + 1: i1] + a[1 + 1:i2] + a[i2 + 1:i3] + a[i3 + 1:] + t1 = tensor_mul(*a1) + t2 = tensor_mul(a[i0], a[i1], a[i2], a[i3]) + t2 = self.rule_C2theta_all(t2) + t = t1*t2 + prev = S.Zero + while t != prev: + prev = t + t = self.rule_thetaithetai(t) + t = t.contract_metric(self.g, True) + t = self.rule_thetaii(t) + return t + + def rule_thetaii(self, t): + """ + theta(k,i_0,..,i_m,-k,j_0,..,j_n) = + theta(i_0,..,i_m)*theta(j_0,..,j_n) - 1/N*theta(i_0,..,i_m,j_0,..,j_n) + + """ + if t.is_TensAdd: + args = t.args + args = [self.rule_thetaii(x) for x in args] + return TensAdd(*args) + r = self.match_thetaii(t) + if not r: + return t + ipos1, ipos2, cpos1 = r + a = t.split() + ct = t._coeff if cpos1 == 0 else S.One + a1 = a[:cpos1] + a[cpos1 + 1:] + t1 = a[cpos1] + indices = t1.get_indices() + if ipos1 < ipos2: + indices = indices[ipos1:] + indices[:ipos1] + i = ipos2 - ipos1 + else: + indices = indices[ipos2:] + indices[:ipos2] + i = ipos1 - ipos2 + indices1 = indices[1:i] + indices2 = indices[i + 1:] + theta = self.theta + N = self.N + if indices1 == []: + t2 = (N - 1/N)*theta(*indices2) + elif indices2 == []: + t2 = (N - 1/N)*theta(*indices1) + else: + t2 = theta(*indices1)*theta(*indices2) - 1/N*theta(*(indices1 + indices2)) + t3 = tensor_mul(*a1) + return t2*t3*ct + + def rule_thetaithetai(self, t): + """ + theta(k,i_0,..,i_m)*theta(k,j_0,...,j_n) = + theta(i_0,...,i_m,j_0,...,j_n) - 1/N*theta(i_0,..,i_m)*theta(j_0,...,j_n) + """ + if t.is_TensAdd: + args = t.args + args = [self.rule_thetaithetai(x) for x in args] + return TensAdd(*args) + + r = self.match_thetaithetai(t) + if not r: + return t + ipos1, ipos2, cpos1, cpos2 = r + a = t.split() + if cpos1 < cpos2: + a1 = a[:cpos1] + a[cpos1 + 1:cpos2] + a[cpos2 + 1:] + else: + a1 = a[:cpos2] + a[cpos2 + 1:cpos1] + a[cpos1 + 1:] + if cpos1 == 0: + ct = t._coeff + elif cpos2 == 0: + ct = t._coeff + else: + ct = S.One + t1 = a[cpos1] + t2 = a[cpos2] + indices1 = t1.get_indices() + indices2 = t2.get_indices() + indices1 = indices1[ipos1 + 1:] + indices1[:ipos1] + indices2 = indices2[ipos2 + 1:] + indices2[:ipos2] + theta = self.theta + N = self.N + t3 = theta(*(indices1 + indices2)) + t4 = theta(*indices1)*theta(*indices2) + t5 = theta(*(indices1 + indices2)) - 1/N*theta(*indices1)*theta(*indices2) + res = tensor_mul(*a1)*t5*ct + return res + + def rule_C2theta_all(self, t): + """ + convert all ``C`` to ``theta`` and apply simplifying rules. + """ + + tC = self.tC + tCs = self.tCs + t = t.substitute_tensor(tC, tCs, True) + prev = S.Zero + while t != prev: + prev = t + t = t.substitute_tensor(tC, tCs, True) + t = self.rule_thetaithetai(t) + t = t.contract_metric(self.g, True) + t = self.rule_thetaii(t) + return t diff --git a/sympy/tensor/tests/test_group_factors.py b/sympy/tensor/tests/test_group_factors.py new file mode 100644 index 000000000000..7fe68b23574d --- /dev/null +++ b/sympy/tensor/tests/test_group_factors.py @@ -0,0 +1,53 @@ +from time import time +from sympy import Symbol, S +from sympy.tensor.tensor import (tensor_indices) +from sympy.tensor.group_factors import SuNGroupFactors, match_CijCij + +""" +References + +[1] P. Cvitanovic "Group Theory" version 9.0.1 +""" + +def test_SuN(): + N = Symbol('N') + SN = SuNGroupFactors(N) + C = SN.C + g = SN.ASuN.metric + i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14 = \ + tensor_indices('i0:15', SN.ASuN) + theta = SN.theta + + t = C(i0,i1,i2)*C(-i0,-i2,i3) + t = SN.rule_C2theta_all(t) + assert t == -2*N*g(i1, i3) + + t = C(i0,i1,i2)*C(-i1,i3,i4)*C(i5,-i0,-i2)*C(-i5,-i4,-i3) + t = SN.rule_C2theta_all(t) + t = t.expand_coeff() + assert t == 4*N**4 - 4*N**2 + + # see eq.(1.1) and page 11 in Ref.[1] see also page 72 + # We apply rule_C_square to two parts of the tensor; + # applying rule_C_square once to the whole tensor it is 2x slower. + # Without applying rule_C_square it is roughly 50x slower. + t1 = C(i0,i1,i2)*C(-i1,i5,i3)*C(-i2,i4,i7)*C(-i3,-i4,i6) + t2 = C(-i6,i10,i11)*C(-i5,-i11,i8)*C(-i7,-i10,i9)*C(-i8,-i9,i12) + t1 = SN.rule_C_square(t1) + t2 = SN.rule_C_square(t2) + t = t1*t2 + t = SN.rule_C2theta_all(t) + t = t.expand_coeff() + assert t == (2*N**4 + 24*N**2)*g(i0, i12) + + # Identically vanishing tensors + # contracted according to the Kuratowski graph eq.(6.59) in Ref. [1] + t = C(i0,i1,i2)*C(-i1,i3,i4)*C(-i3,i7,i5)*C(-i2,-i5,i6)*C(-i4,-i6,i8) + t1 = t.canon_bp() + assert t1 == 0 + + # contracted according to the Peterson graph eq.(6.60) in Ref. [1] + t = C(i0,i1,i2)*C(-i1,i3,i4)*C(-i2,i5,i6)*C(-i3,i7,i8)*C(-i6,-i7,i9)*\ + C(-i8,i10,i13)*C(-i5,-i10,i11)*C(-i4,-i11,i12)*C(-i9,-i12,i14) + t1 = t.canon_bp() + assert t1 == 0 From e72aa787b47937c61993a8ae09c10ab2f4a5d6ad Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Wed, 2 Jan 2013 12:35:54 +0100 Subject: [PATCH 23/47] rule_C_triangle; fixed bug in expand_coeff; added tests and documentation --- sympy/tensor/group_factors.py | 42 +++++++++++++++++++++--- sympy/tensor/tensor.py | 2 +- sympy/tensor/tests/test_group_factors.py | 14 ++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/sympy/tensor/group_factors.py b/sympy/tensor/group_factors.py index 28473c868cce..a057ba65cee5 100644 --- a/sympy/tensor/group_factors.py +++ b/sympy/tensor/group_factors.py @@ -65,21 +65,25 @@ def match_C_square(t, th): class SuNGroupFactors(object): """ - SU(N) + SU(N) generators in any representation satisfy the Lie algebra ``[T(i), T(j)] = I*C(i,j, -k)*T(k)`` - ``tr(T(i)*T(j)) = g(i, j)`` + ``tr(rep, T(i)*T(j)) = a_rep*g(i, j)`` where + ``a_rep`` depends on the representation. - ``g(i, j)`` is the metric in the adjoint representation space; - it is proportional to the identity matrix due to Shur lemma. + ``g(i, j)`` is the metric in the adjoint representation space. - ``T(i, -a, b)`` is the generator in the defining representation ``N`` For ``N > 2`` there is no metric in the defining representation. + Let ``T(i, -a, b)`` be the generator in the defining representation ``N`` + normalized with ``a_rep = 1`` + Define ``theta(i_1, ..., i_n) = tr(T(i_1)*...*T^(i_n)) + Multiplying the Lie algebra relation by ``T(-k)`` and tracing one gets ``C_(i j k) = -I*theta(i, j, k) + I*theta(j, i, k)`` + For ``SU(N)`` one has the relation ``T(i,-d,a)*T(-i,-b,c) =`` ``delta(-b,a)*delta(-d,c) - 1/N*delta(-d,a)*delta(-b,c)`` @@ -161,6 +165,19 @@ def rule_C_triangle(self, t): r = match_C_triangle(t, self.C) if not r: return t + i0, i1, i2 = sorted(r) + a = t.split() + a1 = a[:i0] + a[i0 + 1: i1] + a[1 + 1:i2] + a[i2 + 1:] + t1 = tensor_mul(*a1) + t2 = tensor_mul(a[i0], a[i1], a[i2]) + t2 = self.rule_C2theta_all(t2) + t = t1*t2 + prev = S.Zero + while t != prev: + prev = t + t = self.rule_thetaithetai(t) + t = t.contract_metric(self.g, True) + t = self.rule_thetaii(t) return t def rule_C_square(self, t): @@ -269,6 +286,21 @@ def rule_thetaithetai(self, t): def rule_C2theta_all(self, t): """ convert all ``C`` to ``theta`` and apply simplifying rules. + + Examples + ======== + + >>> from sympy.tensor.group_factors import SuNGroupFactors + >>> from sympy import Symbol + >>> from sympy.tensor.tensor import tensor_indices + >>> N = Symbol('N') + >>> SN = SuNGroupFactors(N) + >>> C = SN.C + >>> i0,i1,i2,i3,i4,i5,i6 = tensor_indices('i0:7', SN.ASuN) + >>> t = C(i0,i1,i2)*C(-i0,-i2,i3) + >>> t = SN.rule_C2theta_all(t) + >>> SN.rule_C2theta_all(t) + (-2*N)*metric(i1, i3) """ tC = self.tC diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 9a0a9b361d62..f9c1f132b410 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -742,7 +742,7 @@ def substitute_tensor(self, t1, t2, subst_all=False): def expand_coeff(self): args = [_tensor_expand_coeff(x) for x in self.args] - return TensAdd(args) + return TensAdd(*args) def _pretty(self): a = [] diff --git a/sympy/tensor/tests/test_group_factors.py b/sympy/tensor/tests/test_group_factors.py index 7fe68b23574d..25ff790e64fe 100644 --- a/sympy/tensor/tests/test_group_factors.py +++ b/sympy/tensor/tests/test_group_factors.py @@ -40,6 +40,20 @@ def test_SuN(): t = t.expand_coeff() assert t == (2*N**4 + 24*N**2)*g(i0, i12) + # without using rule_C_triangle it is 2.5x slower + t = C(i0,i1,i2)*C(-i2,i3,i5)*C(-i1,-i3,i4)*C(-i4,-i5,i6) + t = SN.rule_C_triangle(t) + t = SN.rule_C2theta_all(t) + t = t.expand_coeff() + assert t == 2*N**2*g(i0, i6) + + # first graph in page 72 in Ref. [1] + t = theta(i0,i1,i2,i3)*theta(-i3,-i2,-i1,-i0) + t = SN.rule_C2theta_all(t) + t = t.expand_coeff() + assert (t._coeff - (N**4 - 3*N**2 + 3)*(N**2 - 1)/N**2).expand() == 0 + + # Identically vanishing tensors # contracted according to the Kuratowski graph eq.(6.59) in Ref. [1] t = C(i0,i1,i2)*C(-i1,i3,i4)*C(-i3,i7,i5)*C(-i2,-i5,i6)*C(-i4,-i6,i8) From e6e33391f97bbb6acd4312034258b29efabb1077 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 3 Jan 2013 13:11:05 +0100 Subject: [PATCH 24/47] added tensorhead --- sympy/tensor/tensor.py | 85 ++++++++++++---------- sympy/tensor/tests/test_tensor.py | 116 +++++++++--------------------- 2 files changed, 80 insertions(+), 121 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index f9c1f132b410..ed79c24d62d2 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -340,6 +340,36 @@ def __call__(self, s, anticommuting=False): else: return [TensorHead(name, self, anticommuting) for name in names] +def tensorhead(name, typ, sym, anticommuting=False): + """ + Function generating tensorhead(s). + + ``name`` name or sequence of names (as in ``symbol``) + + ``typ`` index types + + ``sym`` same as ``*args`` in ``tensorsymmetry`` + + ``anticommuting``: + None no commutation rule + False commutes + True anticommutes + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> A = tensorhead('A', [Lorentz]*2, [[1]*2]) + >>> A(a, -b) + A(a, -b) + + """ + sym = tensorsymmetry(*sym) + S = TensorType(typ, sym) + return S(name, anticommuting) + + class TensorHead(Basic): is_commutative = False @@ -407,12 +437,10 @@ def __call__(self, *indices): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) - >>> sym2 = tensorsymmetry([1]*2) - >>> S2 = TensorType([Lorentz]*2, sym2) - >>> A = S2('A') + >>> A = tensorhead('A', [Lorentz]*2, [[1]*2]) >>> t = A(a, -b) """ assert [indices[i].tensortype for i in range(len(indices))] == self.index_types @@ -513,13 +541,10 @@ def substitute_indices(self, *index_tuples): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorType - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) - >>> sym2 = tensorsymmetry([1]*2) - >>> S2 = TensorType([Lorentz]*2, sym2) - >>> A, B = S2('A,B') + >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) >>> t = A(i, k)*B(-k, -j); t A(i, L_0)*B(-L_0, -j) >>> t.substitute_indices((i,j), (j, k)) @@ -641,12 +666,10 @@ class TensAdd(TensExpr): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensorsymmetry, TensorType, tensor_indices, TensAdd + >>> from sympy.tensor.tensor import TensorIndexType, tensorhead, tensor_indices >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) - >>> sym = tensorsymmetry([1]) - >>> S1 = TensorType([Lorentz], sym) - >>> p, q = S1('p,q') + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> t1 = p(a) >>> t2 = q(a) >>> t1 + t2 @@ -875,13 +898,11 @@ def get_indices(self): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym1 = tensorsymmetry([1]) - >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric - >>> p, q = S1('p,q') + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> t = p(m1)*g(m0,m2) >>> t.get_indices() [m1, m0, m2] @@ -926,12 +947,10 @@ def split(self): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) - >>> sym2 = tensorsymmetry([1]*2) - >>> S2 = TensorType([Lorentz]*2, sym2) - >>> A, B = S2('A,B') + >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) >>> t = A(a,b)*B(-b,c) >>> t A(a, L_0)*B(-L_0, c) @@ -1023,13 +1042,11 @@ def __mul__(self, other): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym1 = tensorsymmetry([1]) - >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric - >>> p, q = S1('p,q') + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> t1 = p(m0) >>> t2 = q(-m0) >>> t1*t2 @@ -1161,12 +1178,10 @@ def canon_bp(self): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym2a = tensorsymmetry([2]) - >>> S2 = TensorType([Lorentz]*2, sym2a) - >>> A = S2('A') + >>> A = tensorhead('A', [Lorentz]*2, [[2]]) >>> t = A(m0,-m1)*A(m1,-m0) >>> t.canon_bp() -A(L_0, L_1)*A(-L_0, -L_1) @@ -1319,13 +1334,11 @@ def contract_metric(self, g, contract_all=False): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) - >>> sym1 = tensorsymmetry([1]) - >>> S1 = TensorType([Lorentz], sym1) >>> g = Lorentz.metric - >>> p, q = S1('p,q') + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> t = p(m0)*q(m1)*g(-m0, -m1) >>> t.canon_bp() metric(L_0, L_1)*p(-L_0)*q(-L_1) @@ -1495,12 +1508,10 @@ def riemann_cyclic(t2): Examples ======== - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorsymmetry, TensorType, riemann_cyclic + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead, riemann_cyclic >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) - >>> symr = tensorsymmetry([2, 2]) - >>> R4 = TensorType([Lorentz]*4, symr) - >>> R = R4('R') + >>> R = tensorhead('R', [Lorentz]*4, [[2, 2]]) >>> t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) >>> riemann_cyclic(t) 0 diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 12122e83b320..7c51c5c8cb1e 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -4,7 +4,7 @@ from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, - tensorlist_contract_metric, TensMul, tensorsymmetry) + tensorlist_contract_metric, TensMul, tensorsymmetry, tensorhead) from sympy.utilities.pytest import raises #################### Tests from tensor_can.py ####################### @@ -145,10 +145,8 @@ def test_no_metric_symmetry(): # A^d1_d0 * A^d0_d1 # T_c = A^d0_d1 * A^d1_d0 Lorentz = TensorIndexType('Lorentz', metric=None, dummy_fmt='L') - d0, d1, d2, d3 = tensor_indices('d0 d1 d2 d3', Lorentz) - nsym2 = tensorsymmetry([1], [1]) - NS2 = TensorType([Lorentz]*2, nsym2) - A = NS2('A') + d0, d1, d2, d3 = tensor_indices('d:4', Lorentz) + A = tensorhead('A', [Lorentz]*2, [[1], [1]]) t = A(d1, -d0)*A(d0, -d1) tc = t.canon_bp() assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_0)' @@ -324,12 +322,10 @@ def test_canonicalize1(): def test_riemann_invariants(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11 = \ - tensor_indices(','.join(['d%d' % i for i in range(12)]), Lorentz) + tensor_indices('d0:12', Lorentz) # R^{d0 d1}_{d1 d0}; ord = [d0,-d0,d1,-d1] # T_c = -R^{d0 d1}_{d0 d1} - symr = TensorSymmetry(riemann_bsgs) - R4 = TensorType([Lorentz]*4, symr) - R = R4('R', 0) + R = tensorhead('R', [Lorentz]*4, [[2, 2]]) t = R(d0, d1, -d1, -d0) tc = t.canon_bp() assert str(tc) == '-R(L_0, L_1, -L_0, -L_1)' @@ -349,17 +345,10 @@ def test_riemann_invariants(): def test_riemann_products(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - d0, d1, d2, d3, d4, d5, d6= \ - tensor_indices(','.join(['d%d' % i for i in range(7)]), Lorentz) - a0, a1, a2, a3, a4, a5 = \ - tensor_indices(','.join(['a%d' % i for i in range(6)]), Lorentz) + d0, d1, d2, d3, d4, d5, d6 = tensor_indices('d0:7', Lorentz) + a0, a1, a2, a3, a4, a5 = tensor_indices('a0:6', Lorentz) a, b = tensor_indices('a,b', Lorentz) - symr = TensorSymmetry(riemann_bsgs) - R4 = TensorType([Lorentz]*4, symr) - R = R4('R') - sym1 = tensorsymmetry([1]) - sym2 = tensorsymmetry([1]*2) - sym2a = tensorsymmetry([2]) + R = tensorhead('R', [Lorentz]*4, [[2, 2]]) # R^{a b d0}_d0 = 0 t = R(a, b, d0, -d0) tc = t.canon_bp() @@ -381,8 +370,7 @@ def test_riemann_products(): # R^{d6 d5}_d2^d1 * R^{d4 d0 d2 d3} * A_{d6 d0} A_{d3 d1} * A_{d4 d5} # g = [12,10,5,2, 8,0,4,6, 13,1, 7,3, 9,11,14,15] # T_c = -R^{d0 d1 d2 d3} * R_d0^{d4 d5 d6} * A_{d1 d4}*A_{d2 d5}*A_{d3 d6} - S2 = TensorType([Lorentz]*2, sym2) - V = S2('V') + V = tensorhead('V', [Lorentz]*2, [[1]*2]) t = R(d6, d5, -d2, d1)*R(d4, d0, d2, d3)*V(-d6, -d0)*V(-d3, -d1)*V(-d4, -d5) tc = t.canon_bp() assert str(tc) == '-R(L_0, L_1, L_2, L_3)*R(-L_0, L_4, L_5, L_6)*V(-L_1, -L_4)*V(-L_2, -L_5)*V(-L_3, -L_6)' @@ -398,10 +386,8 @@ def test_canonicalize2(): D = Symbol('D') Eucl = TensorIndexType('Eucl', metric=0, dim=D, dummy_fmt='E') i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14 = \ - tensor_indices('i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14', Eucl) - sym3a = tensorsymmetry([3]) - S3a = TensorType([Eucl]*3, sym3a) - A = S3a('A') + tensor_indices('i0:15', Eucl) + A = tensorhead('A', [Eucl]*3, [[3]]) # two examples from Cvitanovic, Group Theory page 59 # of identities for antisymmetric tensors of rank 3 @@ -429,13 +415,10 @@ def test_TensorIndexType(): D = Symbol('D') G = Metric('g', False) Lorentz = TensorIndexType('Lorentz', metric=G, dim=D, dummy_fmt='L') - m0, m1, m2, m3, m4 = tensor_indices('m0,m1,m2,m3,m4', Lorentz) - sym1 = tensorsymmetry([1]) - S1 = TensorType([Lorentz], sym1) + m0, m1, m2, m3, m4 = tensor_indices('m0:5', Lorentz) sym2 = tensorsymmetry([1]*2) - S2 = TensorType([Lorentz]*2, sym2) g = Lorentz.metric - p = S1('p') + p = tensorhead('p', [Lorentz], [[1]]) assert str(g) == 'g(Lorentz,Lorentz)' t = g(m0, m1)*p(-m1) t1 = t.contract_metric(g) @@ -446,9 +429,7 @@ def test_get_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) assert a != -a - sym2 = tensorsymmetry([1]*2) - S2 = TensorType([Lorentz]*2, sym2) - A, B = S2('A B') + A, B = tensorhead('A B', [Lorentz]*2, [[1]*2]) assert A != B t = A(a,b)*B(-b,c) indices = t.get_indices() @@ -465,18 +446,14 @@ def test_add1(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a,b,d0,d1,i,j,k = tensor_indices('a,b,d0,d1,i,j,k', Lorentz) # A, B symmetric - sym1 = tensorsymmetry([1]) - sym2 = tensorsymmetry([1]*2) - S2 = TensorType([Lorentz]*2, sym2) - A, B = S2('A,B') + A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) t1 = A(b,-d0)*B(d0,a) t2a = B(d0,a) + A(d0, a) t2 = A(b,-d0)*t2a assert str(t2) == 'A(a, L_0)*A(b, -L_0) + A(b, L_0)*B(a, -L_0)' t2b = t2 + t1 assert str(t2b) == '2*A(b, L_0)*B(a, -L_0) + A(a, L_0)*A(b, -L_0)' - S1 = TensorType([Lorentz], sym1) - p, q, r = S1('p,q,r') + p, q, r = tensorhead('p,q,r', [Lorentz], [[1]]) t = q(d0)*2 assert str(t) == '2*q(d0)' t = 2*q(d0) @@ -508,12 +485,8 @@ def test_add1(): def test_add2(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m, n, p, q = tensor_indices('m,n,p,q', Lorentz) - symr = tensorsymmetry([2, 2]) - sym3a = tensorsymmetry([3]) - R4 = TensorType([Lorentz]*4, symr) - S3a = TensorType([Lorentz]*3, sym3a) - R = R4('R') - A = S3a('A') + R = tensorhead('R', [Lorentz]*4, [[2, 2]]) + A = tensorhead('A', [Lorentz]*3, [[3]]) t1 = 2*R(m,n,p,q) - R(m,q,n,p) + R(m,p,n,q) t2 = t1*A(-n,-p,-q) assert t2 == 0 @@ -525,17 +498,13 @@ def test_add2(): def test_substitute_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') i, j, k, l, m, n, p, q = tensor_indices('i,j,k,l,m,n,p,q', Lorentz) - sym2 = tensorsymmetry([1]*2) - S2 = TensorType([Lorentz]*2, sym2) - A, B = S2('A,B') + A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) t = A(i, k)*B(-k, -j) t1 = t.substitute_indices((i,j), (j, k)) t1a = A(j, l)*B(-l, -k) assert t1 == t1a - sym1 = tensorsymmetry([1]) - S1 = TensorType([Lorentz], sym1) - p = S1('p') + p = tensorhead('p', [Lorentz], [[1]]) t = p(i) t1 = t.substitute_indices((j, k)) assert t1 == t @@ -550,13 +519,9 @@ def test_substitute_indices(): def test_substitute_tensor(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) - sym1 = tensorsymmetry([1]) - sym2 = tensorsymmetry([1]*2) - S1 = TensorType([Lorentz], sym1) - S2 = TensorType([Lorentz]*2, sym2) - p, q = S1('p,q') - A = S2('A') + m0, m1, m2, m3 = tensor_indices('m0:4', Lorentz) + p, q = tensorhead('p,q', [Lorentz], [[1]]) + A = tensorhead('A', [Lorentz]*2, [[1]*2]) t = 2*p(m0)*q(m1) t1 = p(m1) t2 = 3*A(m1, m2)*p(-m2) @@ -567,10 +532,9 @@ def test_substitute_tensor(): def test_riemann_cyclic_replace(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - m0,m1,m2,m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + m0,m1,m2,m3 = tensor_indices('m:4', Lorentz) symr = tensorsymmetry([2, 2]) - R4 = TensorType([Lorentz]*4, symr) - R = R4('R') + R = tensorhead('R', [Lorentz]*4, [[2, 2]]) t = R(m0,m2,m1,m3) t1 = riemann_cyclic_replace(t) t1a = -S.One/3*R(m0, m3, m2, m1) + S.One/3*R(m0, m1, m2, m3) + Rational(2,3)*R(m0, m2, m1, m3) @@ -579,9 +543,7 @@ def test_riemann_cyclic_replace(): def test_riemann_cyclic(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') i, j, k, l, m, n, p, q = tensor_indices('i,j,k,l,m,n,p,q', Lorentz) - symr = tensorsymmetry([2, 2]) - R4 = TensorType([Lorentz]*4, symr) - R = R4('R') + R = tensorhead('R', [Lorentz]*4, [[2, 2]]) t = R(i,j,k,l) + R(i,l,j,k) + R(i,k,l,j) - \ R(i,j,l,k) - R(i,l,k,j) - R(i,k,j,l) t2 = t*R(-i,-j,-k,-l) @@ -597,10 +559,8 @@ def test_riemann_cyclic(): def test_div(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - m0,m1,m2,m3 = tensor_indices('m0,m1,m2,m3', Lorentz) - symr = tensorsymmetry([2, 2]) - R4 = TensorType([Lorentz]*4, symr) - R = R4('R') + m0,m1,m2,m3 = tensor_indices('m0:4', Lorentz) + R = tensorhead('R', [Lorentz]*4, [[2, 2]]) t = R(m0,m1,-m1,m3) t1 = t/S(4) assert str(t1) == '1/4*R(m0, L_0, -L_0, m3)' @@ -615,10 +575,8 @@ def test_metric_contract1(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) - sym2 = tensorsymmetry([1]*2) - S2 = TensorType([Lorentz]*2, sym2) g = Lorentz.metric - A, B = S2('A,B') + A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) # case with g with all free indices t1 = A(a,b)*B(-b,c)*g(d, e) @@ -655,11 +613,7 @@ def test_metric_contract1(): Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e, L_0 = tensor_indices('a,b,c,d,e,L_0', Lorentz) g = Lorentz.metric - sym1 = tensorsymmetry([1]) - sym2 = tensorsymmetry([1]*2) - S1 = TensorType([Lorentz], sym1) - S2 = TensorType([Lorentz]*2, sym2) - p, q = S1('p,q') + p, q = tensorhead('p,q', [Lorentz], [[1]]) t1 = g(a,b)*p(c)*p(-c) t2 = 3*g(-a,-b)*q(c)*q(-c) @@ -737,7 +691,7 @@ def test_metric_contract1(): v1 = tensorlist_contract_metric(v, g(d, e)) assert v1 == [p(a), q(b), p(c), g(d, e)] - A = S2('A') + A = tensorhead('A', [Lorentz]*2, [[1]*2]) t = A(a, b)*p(L_0)*g(-a, -b) t1 = t.contract_metric(g) assert str(t1) == 'A(L_1, -L_1)*p(L_0)' or str(t1) == 'A(-L_1, L_1)*p(L_0)' @@ -747,9 +701,7 @@ def test_epsilon(): a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) g = Lorentz.metric epsilon = Lorentz.epsilon - sym1 = tensorsymmetry([1]) - S1 = TensorType([Lorentz], sym1) - p, q, r, s = S1('p,q,r,s') + p, q, r, s = tensorhead('p,q,r,s', [Lorentz], [[1]]) t = epsilon(b,a,c,d) t1 = t.canon_bp() @@ -788,10 +740,6 @@ def test_contract_delta1(): n = Symbol('n') Color = TensorIndexType('Color', metric=None, dim=n, dummy_fmt='C') a, b, c, d, e, f = tensor_indices('a,b,c,d,e,f', Color) - sym1 = tensorsymmetry([1]) - S1 = TensorType([Color], sym1) - sym2 = tensorsymmetry([2]) - S2 = TensorType([Color]*2, sym2) delta = Color.delta def idn(a, b, d, c): From e19897da0e7de3d2dc2620da920239c3052f2657 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Fri, 4 Jan 2013 16:54:10 +0100 Subject: [PATCH 25/47] Added contraction rules for antisymmetric metric. --- sympy/tensor/tensor.py | 158 ++++++++++++++++++++++-------- sympy/tensor/tests/test_tensor.py | 52 +++++++++- 2 files changed, 165 insertions(+), 45 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index ed79c24d62d2..1512607142d0 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -18,6 +18,15 @@ class TensorIndexType(Basic): In these two cases the metric is used to raise and lower indices. + In the case of antisymmetric metric, the following raising and + lowering conventions will be adopted: + + ``psi(a) = g(a, b)*psi(-b); chi(-a) = chi(b)*g(-b, -a) + + ``g(-a, b) = delta(-a, b); g(b, -a) = -delta(a, -b)`` + + where ``delta(-a, b) = delta(b, -a)`` is the Kronecker delta + ``metric = None`` there is no metric; it is not possible to raise or lower indices; e.g. the index of the defining representation of ``SU(N)`` @@ -443,7 +452,8 @@ def __call__(self, *indices): >>> A = tensorhead('A', [Lorentz]*2, [[1]*2]) >>> t = A(a, -b) """ - assert [indices[i].tensortype for i in range(len(indices))] == self.index_types + if not [indices[i].tensortype for i in range(len(indices))] == self.index_types: + raise ValueError('wrong index type') components = [self] free, dum = TensMul.from_indices(*indices) free.sort(key=lambda x: x[0].name) @@ -1201,9 +1211,16 @@ def canon_bp(self): return S.Zero return t.perm2tensor(can, True) - def _contract(self, g, is_metric, contract_all=False): + def _contract(self, g, antisym, contract_all=False): """ helper method for ``contract_metric`` and ``contract_delta`` + + ``g`` metric to be contracted + + ``antisym``: + False symmetric metric + True antisymmetric metric + None delta """ if not self._components: return self @@ -1211,33 +1228,42 @@ def _contract(self, g, is_metric, contract_all=False): a = self.split() typ = g.index_types[0] # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim - for i, tx in enumerate(a): - if tx._components[0] == g: - free_indices_g = [x[0] for x in a[i]._free] - if len(free_indices_g) == 0: + for i, tg in enumerate(a): + if tg._components[0] == g: + tg_free_indices = [x[0] for x in tg._free] + if len(tg_free_indices) == 0: + # g is contracted with itself a1 = a[:i] + a[i + 1:] - t = tensor_mul(*a1)*(typ.dim*a[i]._coeff) + t11 = tensor_mul(*a1) + if typ.dim is None: + raise ValueError('dimension not assigned') + coeff = typ.dim*a[i]._coeff + if antisym and tg._dum[0][0] == 0: + # g(i, -i) = -D + coeff = -coeff + t = tensor_mul(*a1)*coeff if contract_all == True and g in t._components: - return t._contract(g, is_metric, True) + return t._contract(g, antisym, True) return t - # if all metric tensors have only free indices, there is no contraction - for i, tg in enumerate(a): - if tg._components[0] == g: - tg_free_indices = [x[0] for x in tg._free] - if not is_metric: - if tg_free_indices[0].is_contravariant == tg_free_indices[1].is_contravariant: - raise ValueError('both indices are (contra)variant') if all(indx in free_indices for indx in tg_free_indices): continue - break + else: + break else: + # if all metric tensors have only free indices, there is no contraction return self + # tg has one or two indices contracted with other tensors # i position of tg in a coeff = S.One tg_free = tg._free + if antisym: + # order by slot position + tg_free = sorted(tg_free, key=lambda x: x[1]) + if tg_free[0][0] in free_indices or tg_free[1][0] in free_indices: + # tg has one free index if tg_free[0][0] in free_indices: ind_free = tg_free[0][0] ind, ipos1, _ = tg_free[1] @@ -1246,20 +1272,28 @@ def _contract(self, g, is_metric, contract_all=False): ind, ipos1, _ = tg_free[0] ind1 = -ind - # search ind1 in self._dum + # search ind1 in the other component tensors for j, tx in enumerate(a): if ind1 in [x[0] for x in tx._free]: break + # replace ind1 with ind_free free1 = [] for indx, iposx, _ in tx._free: if indx == ind1: free1.append((ind_free, iposx, 0)) else: free1.append((indx, iposx, 0)) - t1 = TensMul(tx._coeff, tx._components, free1, tx._dum) + coeff = tx._coeff + if antisym: + if ind.is_contravariant and ind == tg_free[0][0] or \ + (not ind.is_contravariant) and ind == tg_free[1][0]: + # g(i1, i0)*psi(-i1) = -psi(i0) + # g(-i0, -i1)*psi(i1) = -psi(-i0) + coeff = -coeff + t1 = TensMul(coeff, tx._components, free1, tx._dum) a[j] = t1 a = a[:i] + a[i + 1:] - coeff = coeff*tg._coeff + coeff = tg._coeff res = tensor_mul(*a) else: # tg has two indices contracted with other tensors @@ -1270,29 +1304,41 @@ def _contract(self, g, is_metric, contract_all=False): for k, ty in enumerate(a): if ind2m in [x[0] for x in ty._free]: break + # ty has the index ind2m + ty_free = ty._free[:] if ty._components == [g]: ty_indices = [x[0] for x in ty._free] if all(x in [ind1m, ind2m] for x in ty_indices): - if i < k: - a = a[:i] + a[i+1:k] + a[k+1:] - else: - a = a[:k] + a[k+1:i] + a[i+1:] + # the two `g` are completely contracted + # i < k always + a = a[:i] + a[i+1:k] + a[k+1:] + coeff = coeff*typ.dim*tg._coeff*ty._coeff + if antisym: + ty_free = sorted(ty_free, key=lambda x: x[1]) + if ind1.is_contravariant == ind2.is_contravariant: + # g(i,j)*g(-i,-j) = g(-i,-j)*g(i,j) = dim + # g(i,j)*g(-j,-i) = g(-i,-j)*g(j,i) = -dim + if ind1m == ty_free[1][0]: + coeff = -coeff + else: + # g(-i,j)*g(i,-j) = g(i,-j)^g(-i,j) = -dim + # g(-i,j)*g(-j,i) = g(i,-j)*g(j,i) = dim + if ind1m == ty_free[0][0]: + coeff = -coeff + if a: res = tensor_mul(*a) - res = (coeff*typ.dim*tg._coeff*ty._coeff)*res + res = coeff*res else: - res = coeff*typ.dim*tg._coeff*ty._coeff - res = TensMul(res, [],[],[], is_canon_bp=True) + res = TensMul(coeff, [],[],[], is_canon_bp=True) if contract_all == True and g in res._components: - return res._contract(g, is_metric, True) + return res._contract(g, antisym, True) return res - free2 = [] - # ty._free contains ind2m; replace ind2m with ind1 - # in the fee indices of ty, unless ty has ind1m as free index; - # in that case take away it from free and create a dummy entry - ty_freeindices = [x[0] for x in ty._free] + free2 = [] + ty_freeindices = [x[0] for x in ty_free] if ind1m in ty_freeindices: + # tg has both indices contracted with ty free2 = [(indx, iposx, cposx) for indx, iposx, cposx in ty._free if indx != ind1m and indx != ind2m] dum2 = ty._dum[:] for indx, iposx, _ in ty._free: @@ -1300,15 +1346,41 @@ def _contract(self, g, is_metric, contract_all=False): iposx1 = iposx if indx == ind2m: iposx2 = iposx - # ind1m is covariant - dum2.append((iposx2, iposx1, 0, 0)) + if antisym: + if ind1.is_contravariant == ind2.is_contravariant: + if iposx1 < iposx2: + coeff = -coeff + dum2.append((iposx1, iposx2, 0, 0)) + else: + dum2.append((iposx2, iposx1, 0, 0)) + else: + if iposx1 > iposx2: + coeff = -coeff + dum2.append((iposx2, iposx1, 0, 0)) + else: + dum2.append((iposx1, iposx2, 0, 0)) + else: + dum2.append((iposx1, iposx2, 0, 0)) else: + # replace ind2m with ind1 in the free indices of ty + free2 = [] - for indx, iposx, _ in ty._free: - if indx == ind2m: - free2.append((ind1, iposx, 0)) - else: - free2.append((indx, iposx, 0)) + if not antisym: + for indx, iposx, _ in ty._free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + else: + free2.append((indx, iposx, 0)) + else: + for indx, iposx, _ in ty._free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + if indx.is_contravariant: + coeff = -coeff + else: + free2.append((indx, iposx, 0)) + if not indx.is_contravariant: + coeff = -coeff dum2 = ty._dum t2 = TensMul(ty._coeff, ty._components, free2, dum2) a[k] = t2 @@ -1317,11 +1389,11 @@ def _contract(self, g, is_metric, contract_all=False): res = tensor_mul(*a) res = coeff*res if contract_all == True and g in res._components: - return res._contract(g, is_metric, True) + return res._contract(g, antisym, True) return res def contract_delta(self, delta): - return self._contract(delta, False, True) + return self._contract(delta, None, True) def contract_metric(self, g, contract_all=False): """ @@ -1345,10 +1417,10 @@ def contract_metric(self, g, contract_all=False): >>> t.contract_metric(g).canon_bp() p(L_0)*q(-L_0) """ - if g.index_types[0].metric_antisym != 0: + if g.index_types[0].metric_antisym is None: # TODO case of antisymmetric metric raise NotImplementedError - return self._contract(g, True, contract_all) + return self._contract(g, g.index_types[0].metric_antisym, contract_all) def substitute_tensor(self, t1, t2, subst_all=False): """ diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 7c51c5c8cb1e..0748058c263a 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -571,7 +571,7 @@ def test_div(): t1 = t1/4 assert t1._is_canon_bp -def test_metric_contract1(): +def test_contract_metric1(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) @@ -608,7 +608,7 @@ def test_metric_contract1(): assert t2 == A(a, -a) assert not t2._free -def test_metric_contract1(): +def test_contract_metric2(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e, L_0 = tensor_indices('a,b,c,d,e,L_0', Lorentz) @@ -696,6 +696,54 @@ def test_metric_contract1(): t1 = t.contract_metric(g) assert str(t1) == 'A(L_1, -L_1)*p(L_0)' or str(t1) == 'A(-L_1, L_1)*p(L_0)' +def test_metric_contract3(): + D = Symbol('D') + Spinor = TensorIndexType('Spinor', dim=D, metric=True, dummy_fmt='S') + a0,a1,a2,a3,a4 = tensor_indices('a0:5', Spinor) + C = Spinor.metric + chi, psi = tensorhead('chi,psi', [Spinor], [[1]], 1) + B = tensorhead('B', [Spinor]*2, [[1],[1]]) + + t = C(a0,a1)*B(-a1,-a0) + t1 = t.contract_metric(C) + assert t1 == B(a0, -a0) + + t = C(a0,-a1)*B(a1,-a0) + t1 = t.contract_metric(C) + assert t1 == -B(a0, -a0) + + t = C(-a0,a1)*B(-a1,a0) + t1 = t.contract_metric(C) + assert t1 == -B(a0, -a0) + + t = C(-a0,-a1)*B(a1,a0) + t1 = t.contract_metric(C) + assert t1 == B(a0, -a0) + + t = C(a0,a1)*chi(-a0)*psi(-a1) + t1 = t.contract_metric(C) + assert t1 == -chi(a1)*psi(-a1) + + t = C(a1,a0)*chi(-a0)*psi(-a1) + t1 = t.contract_metric(C) + assert t1 == chi(a1)*psi(-a1) + + t = C(-a1,a0)*chi(-a0)*psi(a1) + t1 = t.contract_metric(C) + assert t1 == chi(-a1)*psi(a1) + + t = C(a0, -a1)*chi(-a0)*psi(a1) + t1 = t.contract_metric(C) + assert t1 == -chi(-a1)*psi(a1) + + t = C(-a0,-a1)*chi(a0)*psi(a1) + t1 = t.contract_metric(C) + assert t1 == chi(-a1)*psi(a1) + + t = C(-a1,-a0)*chi(a0)*psi(a1) + t1 = t.contract_metric(C) + assert t1 == -chi(-a1)*psi(a1) + def test_epsilon(): Lorentz = TensorIndexType('Lorentz', dim=4, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) From 1eeb3ea9661d7400db5e56cf2d391e77e0422f16 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 5 Jan 2013 11:34:01 +0100 Subject: [PATCH 26/47] Introduced set_free_args, fun_eval and __call__ to make TensExpr callable. Added `onlydelta` keyword argument to `_contract`, used in `contract_delta`. Replaced `is_contravariant` with `is_up`. --- sympy/tensor/tensor.py | 232 ++++++++++++++++++++++++++---- sympy/tensor/tests/test_tensor.py | 27 +++- 2 files changed, 231 insertions(+), 28 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 1512607142d0..eb1db97249ec 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -73,6 +73,7 @@ def __new__(cls, name, metric=False, dim=None, eps_dim = None, obj.dummy_fmt = '%s_%%d' % dummy_fmt if metric is None: obj.metric_antisym = None + obj.metric = None else: if metric in (True, False, 0, 1): metric_name = 'metric' @@ -120,7 +121,7 @@ class TensorIndex(Basic): Tensor indices are contructed with the Einstein summation convention. A TensorIndex is chacterized by their ``name``, ``tensortype`` - and ``is_contravariant``. + and ``is_up``. An index can be in contravariant or in covariant form; in the latter case it is represented prepending a ``-`` to the index name. @@ -141,17 +142,17 @@ class TensorIndex(Basic): >>> A(i)*B(-i) A(L_0)*B(-L_0) """ - def __new__(cls, name, tensortype, is_contravariant=True): + def __new__(cls, name, tensortype, is_up=True): - obj = Basic.__new__(cls, name, tensortype, is_contravariant) + obj = Basic.__new__(cls, name, tensortype, is_up) obj.name = name obj.tensortype = tensortype - obj.is_contravariant = is_contravariant + obj.is_up = is_up return obj def _pretty(self): s = self.name - if not self.is_contravariant: + if not self.is_up: s = '-%s' % s return s @@ -160,7 +161,7 @@ def __lt__(self, other): def __neg__(self): t1 = TensorIndex(self.name, self.tensortype, - (not self.is_contravariant)) + (not self.is_up)) return t1 def tensor_indices(s, typ): @@ -544,10 +545,12 @@ def __rdiv__(self, other): __truediv__ = __div__ __rtruediv__ = __rdiv__ - def substitute_indices(self, *index_tuples): + def substitute_indices(self, *index_tuples, **kwargs): """ Return a tensor with free indices substituted according to ``index_tuples`` + ``index_types`` list of tuples ``(old_index, new_index)`` + Examples ======== @@ -563,17 +566,17 @@ def substitute_indices(self, *index_tuples): if self.is_TensMul: free = self._free free1 = [] + gv = [] for j, ipos, cpos in free: for i, v in index_tuples: if i.name == j.name and i.tensortype == j.tensortype: - if i.is_contravariant == j.is_contravariant: + if i.is_up == j.is_up: free1.append((v, ipos, cpos)) else: free1.append((-v, ipos, cpos)) break else: free1.append((j, ipos, cpos)) - return TensMul(self._coeff, self._components, free1, self._dum) if self.is_TensAdd: args = self.args @@ -584,6 +587,7 @@ def substitute_indices(self, *index_tuples): return TensAdd(*args1) + def _tensAdd_collect_terms(args): """ collect TensMul terms differing at most by their coefficient @@ -680,10 +684,11 @@ class TensAdd(TensExpr): >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> a, b = tensor_indices('a,b', Lorentz) >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) - >>> t1 = p(a) - >>> t2 = q(a) - >>> t1 + t2 + >>> t = p(a) + q(a); t p(a) + q(a) + >>> t.set_free_args([a]) + >>> t(b) + p(b) + q(b) """ is_TensAdd = True @@ -712,8 +717,63 @@ def __new__(cls, *args, **kw_args): if len(a) == 1: return a[0] obj._args = tuple(a) + obj.free_args = None + # attribute that can be assigned to make the tensor callable return obj + def set_free_args(self, free_args, sym=None): + """Set arguments to call a tensor. + + ``free_args`` list of the free indices + It must be compatible with ``_free`` + + ``sym`` symmetry of the tensor (to be implemented) + + """ + t0 = self.args[0] + if not sorted([x[0] for x in t0._free]) == sorted(free_args): + raise ValueError('indices do not match') + self.free_args = free_args + + def __call__(self, *indices): + """Returns tensor with ``free_args`` indices replaced by ``indices`` + + If ``free_args`` is not defined or has not the same tensortypes + as ``indices`` an error is raised. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) + >>> g = Lorentz.metric + >>> t = p(i0)*p(i1) + g(i0,i1)*q(i2)*q(-i2) + >>> t.set_free_args([i0,i1]) + >>> t(i0,i2) + metric(i0, i2)*q(L_0)*q(-L_0) + p(i0)*p(i2) + >>> t(i0,i1) - t(i1,i0) + 0 + """ + free_args = self.free_args + indices = list(indices) + if free_args is None: + raise ValueError('`free_args` has not been assigned') + indices = list(indices) + if [x.tensortype for x in indices] != [x.tensortype for x in free_args]: + raise ValueError('incompatible types') + index_tuples = zip(free_args, indices) + a = [x.fun_eval(*index_tuples) for x in self.args] + free_types = set([x.tensortype for x in free_args]) + res = TensAdd(*a) + for typ in free_types: + res = res.contract_delta(typ.delta) + + return res + def canon_bp(self): """ canonicalize using the Butler-Portugal algorithm for canonicalization @@ -773,6 +833,32 @@ def substitute_tensor(self, t1, t2, subst_all=False): args1.append(y) return TensAdd(*args1) + def fun_eval(self, *index_tuples, **kwargs): + """ + Return a tensor with free indices substituted according to ``index_tuples`` + + ``index_types`` list of tuples ``(old_index, new_index)`` + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) + >>> t = A(i, k)*B(-k, -j); t + A(i, L_0)*B(-L_0, -j) + >>> t.substitute_indices((i,j), (j, k)) + A(j, L_0)*B(-L_0, -k) + """ + #fixed = kwargs.pop('fixed', False) + args = self.args + args1 = [] + for x in args: + y = x.fun_eval(*index_tuples) + args1.append(y) + return TensAdd(*args1) + def expand_coeff(self): args = [_tensor_expand_coeff(x) for x in self.args] return TensAdd(*args) @@ -821,6 +907,7 @@ def __new__(cls, coeff, *args, **kw_args): obj._coeff = coeff obj._is_canon_bp = kw_args.get('is_canon_bp', False) + obj.free_args = None return obj def __eq__(self, other): @@ -867,7 +954,7 @@ def from_indices(*indices): for i, index in enumerate(indices): name = index.name typ = index.tensortype - contr = index.is_contravariant + contr = index.is_up if (name, typ) in index_dict: # found a pair of dummy indices is_contr, pos = index_dict[(name, typ)] @@ -891,7 +978,7 @@ def from_indices(*indices): else: dum.append((pos, i, 0, 0)) else: - index_dict[(name, typ)] = index.is_contravariant, i + index_dict[(name, typ)] = index.is_up, i free_indices = [(index, i, 0) for i, index in enumerate(indices) if free[i]] free = sorted(free_indices, key=lambda x: (x[0].tensortype, x[0].name)) @@ -1086,9 +1173,9 @@ def __mul__(self, other): ipos1, cpos1, ind1 = free_dict1[name] ipos2, cpos2, ind2 = free_dict2[name] cpos2 += nc1 - if ind1.is_contravariant == ind2.is_contravariant: + if ind1.is_up == ind2.is_up: raise ValueError('wrong index contruction %s' % ind1) - if ind1.is_contravariant: + if ind1.is_up: new_dummy = (ipos1, ipos2, cpos1, cpos2) else: new_dummy = (ipos2, ipos1, cpos2, cpos1) @@ -1211,7 +1298,7 @@ def canon_bp(self): return S.Zero return t.perm2tensor(can, True) - def _contract(self, g, antisym, contract_all=False): + def _contract(self, g, antisym, contract_all=False, onlydelta=False): """ helper method for ``contract_metric`` and ``contract_delta`` @@ -1231,6 +1318,8 @@ def _contract(self, g, antisym, contract_all=False): for i, tg in enumerate(a): if tg._components[0] == g: tg_free_indices = [x[0] for x in tg._free] + if onlydelta and tg_free_indices[0].is_up == tg_free_indices[1].is_up: + continue if len(tg_free_indices) == 0: # g is contracted with itself a1 = a[:i] + a[i + 1:] @@ -1285,8 +1374,8 @@ def _contract(self, g, antisym, contract_all=False): free1.append((indx, iposx, 0)) coeff = tx._coeff if antisym: - if ind.is_contravariant and ind == tg_free[0][0] or \ - (not ind.is_contravariant) and ind == tg_free[1][0]: + if ind.is_up and ind == tg_free[0][0] or \ + (not ind.is_up) and ind == tg_free[1][0]: # g(i1, i0)*psi(-i1) = -psi(i0) # g(-i0, -i1)*psi(i1) = -psi(-i0) coeff = -coeff @@ -1315,7 +1404,7 @@ def _contract(self, g, antisym, contract_all=False): coeff = coeff*typ.dim*tg._coeff*ty._coeff if antisym: ty_free = sorted(ty_free, key=lambda x: x[1]) - if ind1.is_contravariant == ind2.is_contravariant: + if ind1.is_up == ind2.is_up: # g(i,j)*g(-i,-j) = g(-i,-j)*g(i,j) = dim # g(i,j)*g(-j,-i) = g(-i,-j)*g(j,i) = -dim if ind1m == ty_free[1][0]: @@ -1347,7 +1436,7 @@ def _contract(self, g, antisym, contract_all=False): if indx == ind2m: iposx2 = iposx if antisym: - if ind1.is_contravariant == ind2.is_contravariant: + if ind1.is_up == ind2.is_up: if iposx1 < iposx2: coeff = -coeff dum2.append((iposx1, iposx2, 0, 0)) @@ -1375,11 +1464,11 @@ def _contract(self, g, antisym, contract_all=False): for indx, iposx, _ in ty._free: if indx == ind2m: free2.append((ind1, iposx, 0)) - if indx.is_contravariant: + if indx.is_up: coeff = -coeff else: free2.append((indx, iposx, 0)) - if not indx.is_contravariant: + if not indx.is_up: coeff = -coeff dum2 = ty._dum t2 = TensMul(ty._coeff, ty._components, free2, dum2) @@ -1393,7 +1482,14 @@ def _contract(self, g, antisym, contract_all=False): return res def contract_delta(self, delta): - return self._contract(delta, None, True) + typ = delta.types[0] + g = typ.metric + if g: + antisym = typ.metric_antisym + t = self._contract(g, antisym, contract_all=True, onlydelta=True) + else: + t = self._contract(delta, None, True) + return t def contract_metric(self, g, contract_all=False): """ @@ -1467,6 +1563,94 @@ def substitute_tensors(self, i, j, t, a=None): t = tensor_mul(*a1)*ct return t + def fun_eval(self, *index_tuples, **kwargs): + """ + Return a tensor with free indices substituted according to ``index_tuples`` + + ``index_types`` list of tuples ``(old_index, new_index)`` + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) + >>> t = A(i, k)*B(-k, -j); t + A(i, L_0)*B(-L_0, -j) + >>> t.substitute_indices((i,j), (j, k)) + A(j, L_0)*B(-L_0, -k) + """ + #fixed = kwargs.pop('fixed', False) + free = self._free + free1 = [] + for j, ipos, cpos in free: + # search j in index_tuples + for i, v in index_tuples: + if i == j: + free1.append((v, ipos, cpos)) + break + else: + free1.append((j, ipos, cpos)) + return TensMul(self._coeff, self._components, free1, self._dum) + + def set_free_args(self, free_args, sym=None): + """Set arguments to call a tensor. + + ``free_args`` list of the free indices + It must be compatible with ``_free`` + + ``sym`` symmetry of the tensor (to be implemented) + + """ + if not sorted([x[0] for x in self._free]) == sorted(free_args): + raise ValueError('indices do not match') + self.free_args = free_args + + def __call__(self, *indices): + """Returns tensor with ``free`` indices replaced by ``indices`` + + If ``free`` is not defined or has not the same tensortypes + as ``indices`` an error is raised. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) + >>> g = Lorentz.metric + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> D = Symbol('D') + >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) + >>> g = Lorentz.metric + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) + >>> t = p(i0)*q(i1)*q(-i1) + >>> t.set_free_args([i0]) + >>> t(i1) + p(i1)*q(L_0)*q(-L_0) + """ + free = self.free_args + if free is None: + raise ValueError('`free` has not been assigned') + indices = list(indices) + if [x.tensortype for x in indices] != [x.tensortype for x in free]: + raise ValueError('incompatible types') + #if [x.is_up for x in indices] == [x.is_up for x in free]: + t = self.fun_eval(*zip(free, indices)) + return t + #t = t.contract_delta(delta) + #raise NotImplementedError + def _as_coeff_mul(self): a = self.split() coeff = a[0]._coeff() diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 0748058c263a..d896ac8d652d 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -791,13 +791,13 @@ def test_contract_delta1(): delta = Color.delta def idn(a, b, d, c): - assert a.is_contravariant and d.is_contravariant - assert not (b.is_contravariant or c.is_contravariant) + assert a.is_up and d.is_up + assert not (b.is_up or c.is_up) return delta(a, c)*delta(d, b) def T(a, b, d, c): - assert a.is_contravariant and d.is_contravariant - assert not (b.is_contravariant or c.is_contravariant) + assert a.is_up and d.is_up + assert not (b.is_up or c.is_up) return delta(a, b)*delta(d, c) def P1(a, b, c, d): @@ -821,3 +821,22 @@ def P2(a, b, c, d): t = P1(a, -b, b, -a) t1 = t.contract_delta(delta) assert t1 == n**2 - 1 + +def test_fun(): + D = Symbol('D') + Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + a,b,c,d = tensor_indices('a,b,c,d', Lorentz) + g = Lorentz.metric + + # check that g_{a b; c} = 0 + # example taken from L. Brewin + # "A brief introduction to Cadabra" arxiv:0903.2085 + # dg_{a b c} = \partial_{a} g_{a b} + dg = tensorhead('dg', [Lorentz]*3, [[1], [1]*2]) + # gamma^a_{b c} is the Christoffel symbol + gamma = S.Half*g(a,d)*(dg(-b,-d,-c) + dg(-c,-b,-d) - dg(-d,-b,-c)) + gamma.set_free_args([a, -b, -c]) + # t = g_{a b; c} + t = dg(-c,-a,-b) - g(-a,-d)*gamma(d,-b,-c) - g(-b,-d)*gamma(d,-a,-c) + t = t.contract_metric(g, True) + assert t == 0 From cc86af38d7b99a1c3b6bc03e0bc689603e30e086 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 5 Jan 2013 19:54:27 +0100 Subject: [PATCH 27/47] eliminated `is_Tensor`; factored two helper functions out of `_contract` --- sympy/tensor/dgamma_matr.py | 10 +- sympy/tensor/tensor.py | 323 +++++++++++++++--------------- sympy/tensor/tests/test_tensor.py | 2 +- 3 files changed, 163 insertions(+), 172 deletions(-) diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index 8c3ccb4a8fbd..fc21a297f94e 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -1,6 +1,6 @@ from sympy import Symbol, S, I from sympy.combinatorics import Permutation -from sympy.tensor.tensor import (is_Tensor, TensorIndexType, tensor_indices, +from sympy.tensor.tensor import (TensExpr, TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, tensor_mul, TensMul, TensAdd, TensorHead, tensorlist_contract_metric) @@ -61,7 +61,7 @@ def G5_to_right(self, t): """ move G5 to the right """ - if not is_Tensor(t): + if not isinstance(t, TensExpr): return t if t.is_TensAdd: a = [self.G5_to_right(x) for x in t.args] @@ -219,7 +219,7 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): >>> GM.do_rule1_gamma(t, doall=True) D*(-D + 2) """ - if not is_Tensor(t): + if not isinstance(t, TensExpr): return t if t.is_TensMul: for n in range(nmax + 1): @@ -354,7 +354,7 @@ def do_rule2_gamma(self, t, nmax=1): >>> GM.do_rule2_gamma(t) (-M**2)*G(L_0)*G(-L_0) + G(L_0)*G(-L_0)*p(L_1)*p(-L_1) """ - if not is_Tensor(t): + if not isinstance(t, TensExpr): return t if t.is_TensMul: for n in range(nmax + 1): @@ -392,7 +392,7 @@ def gamma_trace(self, t): >>> gamma_trace(t) -4*metric(m0, m2)*p(L_0)*q(-L_0) + 4*p(m0)*q(m2) + 4*p(m2)*q(m0) """ - if not is_Tensor(t): + if not isinstance(t, TensExpr): return self.gctr*t t = self.G5_to_right(t) if t.is_TensAdd: diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index eb1db97249ec..fd6167b02893 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -4,9 +4,6 @@ from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs -def is_Tensor(x): - return isinstance(x, TensExpr) - class TensorIndexType(Basic): """ @@ -533,7 +530,7 @@ def __rpow__(self, other): def __div__(self, other): other = sympify(other) - if is_Tensor(other): + if isinstance(other, TensExpr): raise ValueError('cannot divide by a tensor') coeff = self._coeff/other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) @@ -545,7 +542,7 @@ def __rdiv__(self, other): __truediv__ = __div__ __rtruediv__ = __rdiv__ - def substitute_indices(self, *index_tuples, **kwargs): + def substitute_indices(self, *index_tuples): """ Return a tensor with free indices substituted according to ``index_tuples`` @@ -566,7 +563,6 @@ def substitute_indices(self, *index_tuples, **kwargs): if self.is_TensMul: free = self._free free1 = [] - gv = [] for j, ipos, cpos in free: for i, v in index_tuples: if i.name == j.name and i.tensortype == j.tensortype: @@ -577,6 +573,7 @@ def substitute_indices(self, *index_tuples, **kwargs): break else: free1.append((j, ipos, cpos)) + return TensMul(self._coeff, self._components, free1, self._dum) if self.is_TensAdd: args = self.args @@ -587,7 +584,6 @@ def substitute_indices(self, *index_tuples, **kwargs): return TensAdd(*args1) - def _tensAdd_collect_terms(args): """ collect TensMul terms differing at most by their coefficient @@ -635,16 +631,16 @@ def _tensAdd_flatten(args): """ flatten TensAdd, coerce terms which are not tensors to tensors """ - if not all(is_Tensor(x) for x in args): + if not all(isinstance(x, TensExpr) for x in args): args1 = [] for x in args: - if is_Tensor(x): + if isinstance(x, TensExpr): if x.is_TensAdd: args1.extend(list(x.args)) else: args1.append(x) - args1 = [x for x in args1 if is_Tensor(x) and x._coeff] - args2 = [x for x in args if not is_Tensor(x)] + args1 = [x for x in args1 if isinstance(x, TensExpr) and x._coeff] + args2 = [x for x in args if not isinstance(x, TensExpr)] t0 = args1[0] if t0.is_TensAdd: t0 = t0.args[0] @@ -735,6 +731,7 @@ def set_free_args(self, free_args, sym=None): raise ValueError('indices do not match') self.free_args = free_args + def __call__(self, *indices): """Returns tensor with ``free_args`` indices replaced by ``indices`` @@ -769,8 +766,6 @@ def __call__(self, *indices): a = [x.fun_eval(*index_tuples) for x in self.args] free_types = set([x.tensortype for x in free_args]) res = TensAdd(*a) - for typ in free_types: - res = res.contract_delta(typ.delta) return res @@ -786,13 +781,13 @@ def canon_bp(self): def __eq__(self, other): other = sympify(other) - if not is_Tensor(other): + if not isinstance(other, TensExpr): if len(self.args) == 1: return self.args[0]._coeff == other - if is_Tensor(other) and other.is_TensMul and other._coeff == 0: + if isinstance(other, TensExpr) and other.is_TensMul and other._coeff == 0: return self == 0 t = self - other - if not is_Tensor(t): + if not isinstance(t, TensExpr): return t == 0 else: if t.is_TensMul: @@ -914,7 +909,7 @@ def __eq__(self, other): if other == 0: return self._coeff == 0 other = sympify(other) - if not is_Tensor(other): + if not isinstance(other, TensExpr): assert not self._components return self._coeff == other res = self - other @@ -1150,7 +1145,7 @@ def __mul__(self, other): p(L_0)*q(-L_0) """ other = sympify(other) - if not is_Tensor(other): + if not isinstance(other, TensExpr): coeff = self._coeff*other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) if other.is_TensAdd: @@ -1353,130 +1348,10 @@ def _contract(self, g, antisym, contract_all=False, onlydelta=False): if tg_free[0][0] in free_indices or tg_free[1][0] in free_indices: # tg has one free index - if tg_free[0][0] in free_indices: - ind_free = tg_free[0][0] - ind, ipos1, _ = tg_free[1] - else: - ind_free = tg_free[1][0] - ind, ipos1, _ = tg_free[0] - - ind1 = -ind - # search ind1 in the other component tensors - for j, tx in enumerate(a): - if ind1 in [x[0] for x in tx._free]: - break - # replace ind1 with ind_free - free1 = [] - for indx, iposx, _ in tx._free: - if indx == ind1: - free1.append((ind_free, iposx, 0)) - else: - free1.append((indx, iposx, 0)) - coeff = tx._coeff - if antisym: - if ind.is_up and ind == tg_free[0][0] or \ - (not ind.is_up) and ind == tg_free[1][0]: - # g(i1, i0)*psi(-i1) = -psi(i0) - # g(-i0, -i1)*psi(i1) = -psi(-i0) - coeff = -coeff - t1 = TensMul(coeff, tx._components, free1, tx._dum) - a[j] = t1 - a = a[:i] + a[i + 1:] - coeff = tg._coeff - res = tensor_mul(*a) + res = _contract_g_with_free_index(a, free_indices, i, tg, tg_free, g, antisym) else: # tg has two indices contracted with other tensors - ind1 = tg_free[0][0] - ind2 = tg_free[1][0] - ind1m = -ind1 - ind2m = -ind2 - for k, ty in enumerate(a): - if ind2m in [x[0] for x in ty._free]: - break - # ty has the index ind2m - ty_free = ty._free[:] - if ty._components == [g]: - ty_indices = [x[0] for x in ty._free] - if all(x in [ind1m, ind2m] for x in ty_indices): - # the two `g` are completely contracted - # i < k always - a = a[:i] + a[i+1:k] + a[k+1:] - coeff = coeff*typ.dim*tg._coeff*ty._coeff - if antisym: - ty_free = sorted(ty_free, key=lambda x: x[1]) - if ind1.is_up == ind2.is_up: - # g(i,j)*g(-i,-j) = g(-i,-j)*g(i,j) = dim - # g(i,j)*g(-j,-i) = g(-i,-j)*g(j,i) = -dim - if ind1m == ty_free[1][0]: - coeff = -coeff - else: - # g(-i,j)*g(i,-j) = g(i,-j)^g(-i,j) = -dim - # g(-i,j)*g(-j,i) = g(i,-j)*g(j,i) = dim - if ind1m == ty_free[0][0]: - coeff = -coeff - - if a: - res = tensor_mul(*a) - res = coeff*res - else: - res = TensMul(coeff, [],[],[], is_canon_bp=True) - if contract_all == True and g in res._components: - return res._contract(g, antisym, True) - return res - - free2 = [] - ty_freeindices = [x[0] for x in ty_free] - if ind1m in ty_freeindices: - # tg has both indices contracted with ty - free2 = [(indx, iposx, cposx) for indx, iposx, cposx in ty._free if indx != ind1m and indx != ind2m] - dum2 = ty._dum[:] - for indx, iposx, _ in ty._free: - if indx == ind1m: - iposx1 = iposx - if indx == ind2m: - iposx2 = iposx - if antisym: - if ind1.is_up == ind2.is_up: - if iposx1 < iposx2: - coeff = -coeff - dum2.append((iposx1, iposx2, 0, 0)) - else: - dum2.append((iposx2, iposx1, 0, 0)) - else: - if iposx1 > iposx2: - coeff = -coeff - dum2.append((iposx2, iposx1, 0, 0)) - else: - dum2.append((iposx1, iposx2, 0, 0)) - else: - dum2.append((iposx1, iposx2, 0, 0)) - else: - # replace ind2m with ind1 in the free indices of ty - - free2 = [] - if not antisym: - for indx, iposx, _ in ty._free: - if indx == ind2m: - free2.append((ind1, iposx, 0)) - else: - free2.append((indx, iposx, 0)) - else: - for indx, iposx, _ in ty._free: - if indx == ind2m: - free2.append((ind1, iposx, 0)) - if indx.is_up: - coeff = -coeff - else: - free2.append((indx, iposx, 0)) - if not indx.is_up: - coeff = -coeff - dum2 = ty._dum - t2 = TensMul(ty._coeff, ty._components, free2, dum2) - a[k] = t2 - a = a[:i] + a[i + 1:] - coeff = coeff*tg._coeff - res = tensor_mul(*a) - res = coeff*res + res = _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, antisym) if contract_all == True and g in res._components: return res._contract(g, antisym, True) return res @@ -1608,25 +1483,14 @@ def set_free_args(self, free_args, sym=None): self.free_args = free_args def __call__(self, *indices): - """Returns tensor with ``free`` indices replaced by ``indices`` + """Returns tensor with ``free_args`` indices replaced by ``indices`` - If ``free`` is not defined or has not the same tensortypes + If ``free_args`` is not defined or has not the same tensortypes as ``indices`` an error is raised. Examples ======== - >>> from sympy import Symbol - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead - >>> D = Symbol('D') - >>> Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') - >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) - >>> g = Lorentz.metric - >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) - - Examples - ======== - >>> from sympy import Symbol >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead >>> D = Symbol('D') @@ -1639,14 +1503,13 @@ def __call__(self, *indices): >>> t(i1) p(i1)*q(L_0)*q(-L_0) """ - free = self.free_args - if free is None: - raise ValueError('`free` has not been assigned') + free_args = self.free_args + if free_args is None: + raise ValueError('`free_args` has not been assigned') indices = list(indices) - if [x.tensortype for x in indices] != [x.tensortype for x in free]: + if [x.tensortype for x in indices] != [x.tensortype for x in free_args]: raise ValueError('incompatible types') - #if [x.is_up for x in indices] == [x.is_up for x in free]: - t = self.fun_eval(*zip(free, indices)) + t = self.fun_eval(*zip(free_args, indices)) return t #t = t.contract_delta(delta) #raise NotImplementedError @@ -1659,11 +1522,6 @@ def _as_coeff_mul(self): t1 = tensor_mul(*a) return coeff, t1 - def expand_coeff(self): - a = self.split() - self._coeff = self._coeff.expand() - return self - def expand_coeff(self): coeff, t = self._as_coeff_mul() return coeff.expand()*t @@ -1696,7 +1554,7 @@ def canon_bp(p): """ Butler-Portugal canonicalization """ - if is_Tensor(p): + if isinstance(p, TensExpr): return p.canon_bp() return p @@ -1712,7 +1570,7 @@ def tensor_mul(*a): return t def _tensor_expand_coeff(p): - if not is_Tensor(p): + if not isinstance(p, TensExpr): return p return p.expand_coeff() @@ -1781,3 +1639,136 @@ def riemann_cyclic(t2): return t3 else: return canon_bp(t3) + +def _contract_g_with_free_index(a, free_indices, i, tg, tg_free, g, antisym): + """ + helper function for _contract + """ + if tg_free[0][0] in free_indices: + ind_free = tg_free[0][0] + ind, ipos1, _ = tg_free[1] + else: + ind_free = tg_free[1][0] + ind, ipos1, _ = tg_free[0] + + ind1 = -ind + # search ind1 in the other component tensors + for j, tx in enumerate(a): + if ind1 in [x[0] for x in tx._free]: + break + # replace ind1 with ind_free + free1 = [] + for indx, iposx, _ in tx._free: + if indx == ind1: + free1.append((ind_free, iposx, 0)) + else: + free1.append((indx, iposx, 0)) + coeff = tx._coeff + if antisym: + if ind.is_up and ind == tg_free[0][0] or \ + (not ind.is_up) and ind == tg_free[1][0]: + # g(i1, i0)*psi(-i1) = -psi(i0) + # g(-i0, -i1)*psi(i1) = -psi(-i0) + coeff = -coeff + t1 = TensMul(coeff, tx._components, free1, tx._dum) + a[j] = t1 + a = a[:i] + a[i + 1:] + coeff = tg._coeff + res = tensor_mul(*a) + return coeff*res + +def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, antisym): + """ + helper function for _contract + """ + coeff = S.One + ind1 = tg_free[0][0] + ind2 = tg_free[1][0] + ind1m = -ind1 + ind2m = -ind2 + for k, ty in enumerate(a): + if ind2m in [x[0] for x in ty._free]: + break + # ty has the index ind2m + ty_free = ty._free[:] + if ty._components == [g]: + ty_indices = [x[0] for x in ty._free] + if all(x in [ind1m, ind2m] for x in ty_indices): + # the two `g` are completely contracted + # i < k always + a = a[:i] + a[i+1:k] + a[k+1:] + coeff = coeff*typ.dim*tg._coeff*ty._coeff + if antisym: + ty_free = sorted(ty_free, key=lambda x: x[1]) + if ind1.is_up == ind2.is_up: + # g(i,j)*g(-i,-j) = g(-i,-j)*g(i,j) = dim + # g(i,j)*g(-j,-i) = g(-i,-j)*g(j,i) = -dim + if ind1m == ty_free[1][0]: + coeff = -coeff + else: + # g(-i,j)*g(i,-j) = g(i,-j)^g(-i,j) = -dim + # g(-i,j)*g(-j,i) = g(i,-j)*g(j,i) = dim + if ind1m == ty_free[0][0]: + coeff = -coeff + + if a: + res = tensor_mul(*a) + res = coeff*res + else: + res = TensMul(coeff, [],[],[], is_canon_bp=True) + return res + + free2 = [] + ty_freeindices = [x[0] for x in ty_free] + if ind1m in ty_freeindices: + # tg has both indices contracted with ty + free2 = [(indx, iposx, cposx) for indx, iposx, cposx in ty._free if indx != ind1m and indx != ind2m] + dum2 = ty._dum[:] + for indx, iposx, _ in ty._free: + if indx == ind1m: + iposx1 = iposx + if indx == ind2m: + iposx2 = iposx + if antisym: + if ind1.is_up == ind2.is_up: + if iposx1 < iposx2: + coeff = -coeff + dum2.append((iposx1, iposx2, 0, 0)) + else: + dum2.append((iposx2, iposx1, 0, 0)) + else: + if iposx1 > iposx2: + coeff = -coeff + dum2.append((iposx2, iposx1, 0, 0)) + else: + dum2.append((iposx1, iposx2, 0, 0)) + else: + dum2.append((iposx1, iposx2, 0, 0)) + else: + # replace ind2m with ind1 in the free indices of ty + + free2 = [] + if not antisym: + for indx, iposx, _ in ty._free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + else: + free2.append((indx, iposx, 0)) + else: + for indx, iposx, _ in ty._free: + if indx == ind2m: + free2.append((ind1, iposx, 0)) + if indx.is_up: + coeff = -coeff + else: + free2.append((indx, iposx, 0)) + if not indx.is_up: + coeff = -coeff + dum2 = ty._dum + + t2 = TensMul(ty._coeff, ty._components, free2, dum2) + a[k] = t2 + a = a[:i] + a[i + 1:] + coeff = coeff*tg._coeff + res = tensor_mul(*a) + return coeff*res diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index d896ac8d652d..b6b78052aae8 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -831,7 +831,7 @@ def test_fun(): # check that g_{a b; c} = 0 # example taken from L. Brewin # "A brief introduction to Cadabra" arxiv:0903.2085 - # dg_{a b c} = \partial_{a} g_{a b} + # dg_{a b c} = \partial_{a} g_{b c} is symmetric in b, c dg = tensorhead('dg', [Lorentz]*3, [[1], [1]*2]) # gamma^a_{b c} is the Christoffel symbol gamma = S.Half*g(a,d)*(dg(-b,-d,-c) + dg(-c,-b,-d) - dg(-d,-b,-c)) From 79e8cfcf8a7f70f91e262150ac5c3991cb3e83fc Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sun, 6 Jan 2013 09:26:52 +0100 Subject: [PATCH 28/47] eliminated set_free_args --- sympy/tensor/tensor.py | 59 +++++++------------------------ sympy/tensor/tests/test_tensor.py | 9 +++-- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index fd6167b02893..0aee5e836c9f 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -682,7 +682,6 @@ class TensAdd(TensExpr): >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> t = p(a) + q(a); t p(a) + q(a) - >>> t.set_free_args([a]) >>> t(b) p(b) + q(b) """ @@ -713,30 +712,16 @@ def __new__(cls, *args, **kw_args): if len(a) == 1: return a[0] obj._args = tuple(a) - obj.free_args = None # attribute that can be assigned to make the tensor callable return obj - def set_free_args(self, free_args, sym=None): - """Set arguments to call a tensor. - - ``free_args`` list of the free indices - It must be compatible with ``_free`` - - ``sym`` symmetry of the tensor (to be implemented) - - """ - t0 = self.args[0] - if not sorted([x[0] for x in t0._free]) == sorted(free_args): - raise ValueError('indices do not match') - self.free_args = free_args + @property + def free_args(self): + return self.args[0].free_args def __call__(self, *indices): - """Returns tensor with ``free_args`` indices replaced by ``indices`` - - If ``free_args`` is not defined or has not the same tensortypes - as ``indices`` an error is raised. + """Returns tensor with ordered free indices replaced by ``indices`` Examples ======== @@ -749,7 +734,6 @@ def __call__(self, *indices): >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> g = Lorentz.metric >>> t = p(i0)*p(i1) + g(i0,i1)*q(i2)*q(-i2) - >>> t.set_free_args([i0,i1]) >>> t(i0,i2) metric(i0, i2)*q(L_0)*q(-L_0) + p(i0)*p(i2) >>> t(i0,i1) - t(i1,i0) @@ -757,14 +741,12 @@ def __call__(self, *indices): """ free_args = self.free_args indices = list(indices) - if free_args is None: - raise ValueError('`free_args` has not been assigned') - indices = list(indices) if [x.tensortype for x in indices] != [x.tensortype for x in free_args]: raise ValueError('incompatible types') + if indices == free_args: + return self index_tuples = zip(free_args, indices) a = [x.fun_eval(*index_tuples) for x in self.args] - free_types = set([x.tensortype for x in free_args]) res = TensAdd(*a) return res @@ -902,9 +884,12 @@ def __new__(cls, coeff, *args, **kw_args): obj._coeff = coeff obj._is_canon_bp = kw_args.get('is_canon_bp', False) - obj.free_args = None return obj + @property + def free_args(self): + return sorted([x[0] for x in self._free]) + def __eq__(self, other): if other == 0: return self._coeff == 0 @@ -1469,24 +1454,9 @@ def fun_eval(self, *index_tuples, **kwargs): free1.append((j, ipos, cpos)) return TensMul(self._coeff, self._components, free1, self._dum) - def set_free_args(self, free_args, sym=None): - """Set arguments to call a tensor. - - ``free_args`` list of the free indices - It must be compatible with ``_free`` - - ``sym`` symmetry of the tensor (to be implemented) - - """ - if not sorted([x[0] for x in self._free]) == sorted(free_args): - raise ValueError('indices do not match') - self.free_args = free_args def __call__(self, *indices): - """Returns tensor with ``free_args`` indices replaced by ``indices`` - - If ``free_args`` is not defined or has not the same tensortypes - as ``indices`` an error is raised. + """Returns tensor with ordered free indices replaced by ``indices`` Examples ======== @@ -1499,20 +1469,17 @@ def __call__(self, *indices): >>> g = Lorentz.metric >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) >>> t = p(i0)*q(i1)*q(-i1) - >>> t.set_free_args([i0]) >>> t(i1) p(i1)*q(L_0)*q(-L_0) """ free_args = self.free_args - if free_args is None: - raise ValueError('`free_args` has not been assigned') indices = list(indices) if [x.tensortype for x in indices] != [x.tensortype for x in free_args]: raise ValueError('incompatible types') + if indices == free_args: + return self t = self.fun_eval(*zip(free_args, indices)) return t - #t = t.contract_delta(delta) - #raise NotImplementedError def _as_coeff_mul(self): a = self.split() diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index b6b78052aae8..8244bd2650b9 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -825,9 +825,15 @@ def P2(a, b, c, d): def test_fun(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') - a,b,c,d = tensor_indices('a,b,c,d', Lorentz) + a,b,c,d,e = tensor_indices('a,b,c,d,e', Lorentz) g = Lorentz.metric + p, q = tensorhead('p q', [Lorentz], [[1]]) + t = q(c)*p(a)*q(b) + g(a,b)*g(c,d)*q(-d) + assert t(a,b,c) == t + assert t - t(b,a,c) == q(c)*p(a)*q(b) - q(c)*p(b)*q(a) + assert t(b,c,d) == q(d)*p(b)*q(c) + g(b,c)*g(d,e)*q(-e) + # check that g_{a b; c} = 0 # example taken from L. Brewin # "A brief introduction to Cadabra" arxiv:0903.2085 @@ -835,7 +841,6 @@ def test_fun(): dg = tensorhead('dg', [Lorentz]*3, [[1], [1]*2]) # gamma^a_{b c} is the Christoffel symbol gamma = S.Half*g(a,d)*(dg(-b,-d,-c) + dg(-c,-b,-d) - dg(-d,-b,-c)) - gamma.set_free_args([a, -b, -c]) # t = g_{a b; c} t = dg(-c,-a,-b) - g(-a,-d)*gamma(d,-b,-c) - g(-b,-d)*gamma(d,-a,-c) t = t.contract_metric(g, True) From f5a7a38b455cc090749aed3626c7114c4a30ac50 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Mon, 7 Jan 2013 16:42:14 +0100 Subject: [PATCH 29/47] fixed bug in TensAdd.__new__; factored a function from _contract; eliminated the option `onlydelta` from _contract --- sympy/tensor/tensor.py | 59 ++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 0aee5e836c9f..0091a558fdbf 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -701,18 +701,21 @@ def __new__(cls, *args, **kw_args): if len(args) == 1 and args[0].is_TensMul: obj._args = tuple(args) return obj - args.sort(key=lambda x: (x._components, x._free, x._dum)) args = [x.canon_bp() for x in args if x] args = [x for x in args if x] if not args: return S.Zero + + # collect canonicalized terms + args.sort(key=lambda x: (x._components, x._free, x._dum)) a = _tensAdd_collect_terms(args) + if not a: return S.Zero + # it there is only a component tensor return it if len(a) == 1: return a[0] obj._args = tuple(a) - # attribute that can be assigned to make the tensor callable return obj @property @@ -769,6 +772,7 @@ def __eq__(self, other): if isinstance(other, TensExpr) and other.is_TensMul and other._coeff == 0: return self == 0 t = self - other + if not isinstance(t, TensExpr): return t == 0 else: @@ -800,7 +804,7 @@ def contract_metric(self, g, contract_all=False): if len(args) == 1: return args[0] t = TensAdd(*args) - return t.canon_bp() + return canon_bp(t) def substitute_tensor(self, t1, t2, subst_all=False): args = self.args @@ -1278,7 +1282,7 @@ def canon_bp(self): return S.Zero return t.perm2tensor(can, True) - def _contract(self, g, antisym, contract_all=False, onlydelta=False): + def _contract(self, g, antisym, contract_all=False): """ helper method for ``contract_metric`` and ``contract_delta`` @@ -1294,33 +1298,21 @@ def _contract(self, g, antisym, contract_all=False, onlydelta=False): free_indices = [x[0] for x in self._free] a = self.split() typ = g.index_types[0] - # if a component tensor of a has 2 dummy indices, it is g(d,-d) = dim for i, tg in enumerate(a): if tg._components[0] == g: - tg_free_indices = [x[0] for x in tg._free] - if onlydelta and tg_free_indices[0].is_up == tg_free_indices[1].is_up: - continue - if len(tg_free_indices) == 0: - # g is contracted with itself - a1 = a[:i] + a[i + 1:] - t11 = tensor_mul(*a1) - if typ.dim is None: - raise ValueError('dimension not assigned') - coeff = typ.dim*a[i]._coeff - if antisym and tg._dum[0][0] == 0: - # g(i, -i) = -D - coeff = -coeff - t = tensor_mul(*a1)*coeff + tg_free = [x[0] for x in tg._free] + if len(tg_free) == 0: + t = _contract_g_with_itself(a, i, tg, tg_free, g, antisym) if contract_all == True and g in t._components: return t._contract(g, antisym, True) return t - - if all(indx in free_indices for indx in tg_free_indices): + if all(indx in free_indices for indx in tg_free): continue else: break else: - # if all metric tensors have only free indices, there is no contraction + # if all metric tensors have only free indices, + # there is no contraction return self # tg has one or two indices contracted with other tensors @@ -1343,12 +1335,7 @@ def _contract(self, g, antisym, contract_all=False, onlydelta=False): def contract_delta(self, delta): typ = delta.types[0] - g = typ.metric - if g: - antisym = typ.metric_antisym - t = self._contract(g, antisym, contract_all=True, onlydelta=True) - else: - t = self._contract(delta, None, True) + t = self._contract(delta, None, True) return t def contract_metric(self, g, contract_all=False): @@ -1607,6 +1594,22 @@ def riemann_cyclic(t2): else: return canon_bp(t3) +def _contract_g_with_itself(a, i, tg, tg_free, g, antisym): + """ + helper function for _contract + """ + typ = g.index_types[0] + a1 = a[:i] + a[i + 1:] + t11 = tensor_mul(*a1) + if typ.dim is None: + raise ValueError('dimension not assigned') + coeff = typ.dim*a[i]._coeff + if antisym and tg._dum[0][0] == 0: + # g(i, -i) = -D + coeff = -coeff + t = tensor_mul(*a1)*coeff + return t + def _contract_g_with_free_index(a, free_indices, i, tg, tg_free, g, antisym): """ helper function for _contract From 63d396972dd71752cd6ac3f58cd879ed0652f4e9 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 8 Jan 2013 13:18:35 +0100 Subject: [PATCH 30/47] TensorManager used to define general commutation properties; added documentation; cleaned up the code. --- sympy/tensor/dgamma_matr.py | 6 +- sympy/tensor/tensor.py | 263 +++++++++++++++++++++--------- sympy/tensor/tests/test_tensor.py | 29 +++- 3 files changed, 217 insertions(+), 81 deletions(-) diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index fc21a297f94e..5def70fb91ef 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -47,10 +47,10 @@ def __init__(self, typ, gctr=4, g5c=I): self.typ = typ sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) S1 = TensorType([typ], sym1) - self.G = S1('G', None) + self.G = S1('G', 2) sym0 = TensorSymmetry(([], [Permutation(1)])) S0 = TensorType([], sym0) - self.Gamma5 = S0('G5', None) + self.Gamma5 = S0('G5', 2) self.G5 = TensMul(S.One, [self.Gamma5], [], []) self.g5c = g5c self.epsilon = typ.epsilon @@ -256,7 +256,7 @@ def match2_gamma(self, t, n): p_pos2 = dx[3] if p_pos1 > 0 and p_pos2 > 0: if components[p_pos1] == components[p_pos2] \ - and components[p_pos1].anticomm == 0: + and components[p_pos1].comm == 0: return i, p_pos1, p_pos2 return None diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 0091a558fdbf..ba34493c42e4 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -1,8 +1,136 @@ +""" +This module defines tensors with abstract index notation. + +The abstract index notation has been first formalized by Penrose. + +Tensor indices are formal objects, with a tensor type; there is no +notion of index range, it is only possible to assign the dimension, +used to trace the Kronecker delta; the dimension can be a Symbol. + +The Einstein summation convention is used. +The covariant indices are indicated with a minus sign in front of the index. + +For instance the tensor ``t = p(a)*A(b,c)*q(-c)`` has the index ``c`` +contracted. + +A tensor expression ``t`` can be called; called with its +indices in sorted order it is equal to itself: +in the above example ``t(a, b) == t``; +one can call ``t`` with different indices; ``t(c, d) == p(c)*A(d,a)*q(-a)``. + +The contracted indices are dummy indices, internally they have no name, +the indices being represented by a graph-like structure. + +Tensors are put in canonical form using ``canon_bp``, which uses +the Butler-Portugal algorithm for canonicalization. + +If there is a (anti)symmetric metric, the indices can be raised and +lowered when the tensor is put in canonical form. + + + +TODO: + +* better integration in SymPy + +* term collection + +* introduce scalars: +for example in ``p(a)*p(b)/(p**2 + m**2)`` the scalar ``p**2`` +is equivalent to the tensor ``p(c)*p(-c)``, but can appear in the denominator. + +* introduce tensor symmetrizers and algebraic operations on them + +* Rule for contraction of Levi-Civita ``epsilon`` tensors: +one must intoduce generalized Kronecker deltas to do this properly + +* develop a Young tableaux model: +one of its uses is in ``tensorsymmetry`` to provide the symmetry of the tensor +using the Young diagrams +""" + + + from collections import defaultdict from sympy.core import Basic, sympify, Add, Mul, S from sympy.core.symbol import Symbol, symbols from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs +class _TensorManager(object): + def __init__(self, comm=[defaultdict(int) for i in range(2)]): + self._comm = comm + for i in range(len(self._comm)): + self._comm[0][i] = 0 + self._comm[1][0] = 0 + self._comm[1][1] = 0 + + def set_comm(self, i, j, c): + """ + set the commutation parameter ``c`` for commutation groups ``i, j`` + + ``c`` commutation number + 0 commuting + 1 anticommuting + None no commutation property + + Tensors in the group ``i=0`` commute with any other tensor. + Tensors in the group ``i=1`` anticommute within that group. + For the remaining cases, use this method to set the commutation rules; + by default ``c=None``. + + Examples + ======== + + ``G`` and ``GH`` do not commute with themselves and commute with + each other; A is commuting. + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead, TensorManager + >>> Lorentz = TensorIndexType('Lorentz') + >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) + >>> A = tensorhead('A', [Lorentz], [[1]]) + >>> G = tensorhead('G', [Lorentz], [[1]], 2) + >>> GH = tensorhead('GH', [Lorentz], [[1]], 3) + >>> TensorManager.set_comm(2, 3, 0) + >>> (GH(i1)*G(i0)).canon_bp() + G(i0)*GH(i1) + >>> (G(i1)*G(i0)).canon_bp() + G(i1)*G(i0) + >>> (G(i1)*A(i0)).canon_bp() + A(i0)*G(i1) + """ + assert isinstance(i, int) and isinstance(j, int) and i >= 2 and j >= 2 + assert c is None or isinstance(c, int) and c >= 0 + m = max(i,j) + 1 + if len(self._comm) < m: + self._comm.extend([{} for k in range(m - len(self._comm))]) + if not self._comm[i]: + self._comm[i][0] = 0 + self._comm[i][j] = c + self._comm[j][i] = c + + def set_comms(self, *args): + for i, j, c in range(len(args)): + set_comm(self, i, j, c) + + def get_comm(self, i, j): + """ + Return the commutation parameter for commutation groups ``i, j`` + + see ``_TensorManager.set_comm`` + """ + try: + return self._comm[i].get(j, 0 if i*j == 0 else None) + except IndexError: + if j == 0: + return 0 + return None + + + def clear(self): + for i in range(2, len(self._comm)): + self._comm[i].clear() + +TensorManager = _TensorManager() class TensorIndexType(Basic): @@ -309,15 +437,14 @@ def __new__(cls, index_types, symmetry, **kw_args): def __str__(self): return 'TensorType(%s)' %([str(x) for x in self.index_types]) - def __call__(self, s, anticommuting=False): + def __call__(self, s, comm=0): """ Return a TensorHead object or a list of TensorHead objects. ``s`` name or string of names - ``anticommuting``: - None no commutation rule - False commutes - True anticommutes + + ``comm``: commutation group number + see ``_TensorManager.set_comm`` Examples ======== @@ -332,7 +459,7 @@ def __call__(self, s, anticommuting=False): >>> S2 = TensorType([Lorentz]*2, sym2) >>> V = S2('V') >>> W = S2('W', 1) - >>> G = S2('G', None) + >>> G = S2('G', 2) >>> canon_bp(V(a, b)*V(-b, -a)) V(L_0, L_1)*V(-L_0, -L_1) >>> canon_bp(W(a, b)*W(-b, -a)) @@ -343,11 +470,11 @@ def __call__(self, s, anticommuting=False): else: raise ValueError('expecting a string') if len(names) == 1: - return TensorHead(names[0], self, anticommuting) + return TensorHead(names[0], self, comm) else: - return [TensorHead(name, self, anticommuting) for name in names] + return [TensorHead(name, self, comm) for name in names] -def tensorhead(name, typ, sym, anticommuting=False): +def tensorhead(name, typ, sym, comm=0): """ Function generating tensorhead(s). @@ -357,10 +484,9 @@ def tensorhead(name, typ, sym, anticommuting=False): ``sym`` same as ``*args`` in ``tensorsymmetry`` - ``anticommuting``: - None no commutation rule - False commutes - True anticommutes + ``comm``: commutation group number + see ``_TensorManager.set_comm`` + Examples ======== @@ -374,13 +500,13 @@ def tensorhead(name, typ, sym, anticommuting=False): """ sym = tensorsymmetry(*sym) S = TensorType(typ, sym) - return S(name, anticommuting) + return S(name, comm) class TensorHead(Basic): is_commutative = False - def __new__(cls, name, typ, anticommuting, **kw_args): + def __new__(cls, name, typ, comm, **kw_args): """ tensor with given name, index types, symmetry, commutation rule @@ -388,10 +514,8 @@ def __new__(cls, name, typ, anticommuting, **kw_args): ``typ`` list of TensorIndexType - ``anticommuting`` commutation property - False commuting tensor - True anticommuting tensor - None no commutation rule + ``comm`` commutation group number + see ``_TensorManager.set_comm`` Examples ======== @@ -408,7 +532,7 @@ def __new__(cls, name, typ, anticommuting, **kw_args): obj.rank = len(obj.index_types) obj.types = typ.types obj.symmetry = typ.symmetry - obj.anticomm = anticommuting + obj.comm = comm return obj name = property(lambda self: self.args[0]) @@ -422,16 +546,9 @@ def commutes_with(self, other): Returns 0 (1) if self and other (anti)commute. Returns None if self and other do not (anti)commute. - - TODO: it should be possible to assign rules for commutations - between tensors, to be used here. """ - if self.anticomm == 0 or other.anticomm == 0: - return 0 - if self.anticomm == 1 and other.anticomm == 1: - return 1 - return None - + r = TensorManager.get_comm(self.comm, other.comm) + return r def __str__(self): return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) @@ -760,7 +877,6 @@ def canon_bp(self): under monoterm symmetries. """ args = [x.canon_bp() for x in self.args] - args.sort(key=lambda x: (x._components, x._free, x._dum)) res = TensAdd(*args) return res @@ -814,7 +930,7 @@ def substitute_tensor(self, t1, t2, subst_all=False): args1.append(y) return TensAdd(*args1) - def fun_eval(self, *index_tuples, **kwargs): + def fun_eval(self, *index_tuples): """ Return a tensor with free indices substituted according to ``index_tuples`` @@ -832,7 +948,6 @@ def fun_eval(self, *index_tuples, **kwargs): >>> t.substitute_indices((i,j), (j, k)) A(j, L_0)*B(-L_0, -k) """ - #fixed = kwargs.pop('fixed', False) args = self.args args1 = [] for x in args: @@ -945,7 +1060,6 @@ def from_indices(*indices): # check consistency and update free if is_contr: if contr: - print 'ERR indices=', indices raise ValueError('two equal contravariant indices in slots %d and %d' %(pos, i)) else: free[pos] = False @@ -955,7 +1069,6 @@ def from_indices(*indices): free[pos] = False free[i] = False else: - print 'ERR indices=', indices raise ValueError('two equal covariant indices in slots %d and %d' %(pos, i)) if contr: dum.append((i, pos, 0, 0)) @@ -964,9 +1077,8 @@ def from_indices(*indices): else: index_dict[(name, typ)] = index.is_up, i - free_indices = [(index, i, 0) for i, index in enumerate(indices) if free[i]] - free = sorted(free_indices, key=lambda x: (x[0].tensortype, x[0].name)) - + free = [(index, i, 0) for i, index in enumerate(indices) if free[i]] + free.sort() return free, dum def get_indices(self): @@ -993,7 +1105,7 @@ def get_indices(self): pos = 0 vpos = [] components = self._components - for t in self._components: + for t in components: vpos.append(pos) pos += t.rank cdt = defaultdict(int) @@ -1044,7 +1156,7 @@ def split(self): if not components: return [TensMul(self._coeff, [], [], [])] res = [] - for t in self._components: + for t in components: t1 = t(*indices[pos:pos + t.rank]) pos += t.rank res.append(t1) @@ -1066,7 +1178,7 @@ def canon_args(self): pos = 0 vpos = [] components = self._components - for t in self._components: + for t in components: vpos.append(pos) pos += t.rank # ordered indices: first the free indices, ordered by types @@ -1110,7 +1222,11 @@ def canon_args(self): numtyp.append([prev, 1]) v = [] for h, n in numtyp: - v.append((h.symmetry.base, h.symmetry.generators, n, h.anticomm)) + if h.comm == 0 or h.comm == 1: + comm = h.comm + else: + comm = TensorManager.get_comm(h.comm, h.comm) + v.append((h.symmetry.base, h.symmetry.generators, n, comm)) return _af_new(g), dummies, msym, v def __mul__(self, other): @@ -1179,7 +1295,7 @@ def sorted_components(self): for i in range(n): for j in range(n, i, -1): c = cv[j-1][0].commutes_with(cv[j][0]) - if c == None: + if c not in [0, 1]: continue if (cv[j-1][0].types, cv[j-1][0].name) > \ (cv[j][0].types, cv[j][0].name): @@ -1191,13 +1307,10 @@ def sorted_components(self): perm_inv = [x[1] for x in cv] perm = _af_invert(perm_inv) free = [(ind, i, perm[c]) for ind, i, c in self._free] + free.sort() dum = [(i1, i2, perm[c1], perm[c2]) for i1, i2, c1, c2 in self._dum] - free.sort(key = lambda x: (x[0].tensortype, x[0].name)) dum.sort(key = lambda x: components[x[2]].index_types[x[0]]) - if sign == -1: - coeff = -self._coeff - else: - coeff = self._coeff + coeff = -self._coeff if sign == -1 else self._coeff t = TensMul(coeff, components, free, dum) return t @@ -1215,11 +1328,11 @@ def perm2tensor(self, g, canon_bp=False): vpos = [] components = self._components pos = 0 - for t in self._components: + for t in components: vpos.append(pos) pos += t.rank - free_indices = [x[0] for x in self._free] - sorted_free = sorted(free_indices, key=lambda x:(x.tensortype, x.name)) + sorted_free = [x[0] for x in self._free] + sorted_free.sort() nfree = len(sorted_free) rank = self.rank indices = [None]*rank @@ -1311,7 +1424,7 @@ def _contract(self, g, antisym, contract_all=False): else: break else: - # if all metric tensors have only free indices, + # all metric tensors have only free indices, # there is no contraction return self @@ -1361,7 +1474,6 @@ def contract_metric(self, g, contract_all=False): p(L_0)*q(-L_0) """ if g.index_types[0].metric_antisym is None: - # TODO case of antisymmetric metric raise NotImplementedError return self._contract(g, g.index_types[0].metric_antisym, contract_all) @@ -1410,7 +1522,7 @@ def substitute_tensors(self, i, j, t, a=None): t = tensor_mul(*a1)*ct return t - def fun_eval(self, *index_tuples, **kwargs): + def fun_eval(self, *index_tuples): """ Return a tensor with free indices substituted according to ``index_tuples`` @@ -1529,27 +1641,6 @@ def _tensor_expand_coeff(p): return p.expand_coeff() -def tensorlist_contract_metric(a, tg): - """ - contract `tg` with a tensor in the list `a = t.split()` - """ - ind1, ind2 = [x[0] for x in tg._free] - mind1 = -ind1 - mind2 = -ind2 - for i in range(len(a)): - t1 = a[i] - for j in range(len(t1._free)): - indx, ipos, _ = t1._free[j] - if indx == mind1 or indx == mind2: - ind3 = ind2 if indx == mind1 else ind1 - free1 = t1._free[:] - free1[j] = (ind3, ipos, 0) - t2 = TensMul(t1._coeff, t1._components, free1, t1._dum) - a[i] = t2 - return a - a.append(tg) - return a - def riemann_cyclic_replace(t_r): """ @@ -1594,6 +1685,30 @@ def riemann_cyclic(t2): else: return canon_bp(t3) + + +def tensorlist_contract_metric(a, tg): + """ + contract `tg` with a tensor in the list `a = t.split()` + Only for symmetric metric. + """ + ind1, ind2 = [x[0] for x in tg._free] + mind1 = -ind1 + mind2 = -ind2 + for i in range(len(a)): + t1 = a[i] + for j in range(len(t1._free)): + indx, ipos, _ = t1._free[j] + if indx == mind1 or indx == mind2: + ind3 = ind2 if indx == mind1 else ind1 + free1 = t1._free[:] + free1[j] = (ind3, ipos, 0) + t2 = TensMul(t1._coeff, t1._components, free1, t1._dum) + a[i] = t2 + return a + a.append(tg) + return a + def _contract_g_with_itself(a, i, tg, tg_free, g, antisym): """ helper function for _contract diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 8244bd2650b9..89b2cf59b928 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -4,7 +4,7 @@ from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, - tensorlist_contract_metric, TensMul, tensorsymmetry, tensorhead) + tensorlist_contract_metric, TensMul, tensorsymmetry, tensorhead, TensorManager) from sympy.utilities.pytest import raises #################### Tests from tensor_can.py ####################### @@ -288,9 +288,9 @@ def test_canonicalize1(): S3a = TensorType([Lorentz]*3, sym3a) alpha, beta, gamma, mu, nu, rho = \ tensor_indices('alpha,beta,gamma,mu,nu,rho', Lorentz) - Gamma = S1('Gamma', None) - Gamma2 = S2a('Gamma', None) - Gamma3 = S3a('Gamma', None) + Gamma = S1('Gamma', 2) + Gamma2 = S2a('Gamma', 2) + Gamma3 = S3a('Gamma', 2) t = Gamma2(-mu,-nu)*Gamma(rho)*Gamma3(nu, mu, alpha) tc = t.canon_bp() assert str(tc) == '-Gamma(L_0, L_1)*Gamma(rho)*Gamma(alpha, -L_0, -L_1)' @@ -845,3 +845,24 @@ def test_fun(): t = dg(-c,-a,-b) - g(-a,-d)*gamma(d,-b,-c) - g(-b,-d)*gamma(d,-a,-c) t = t.contract_metric(g, True) assert t == 0 + +def test_TensorManager(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + LorentzH = TensorIndexType('LorentzH', dummy_fmt='LH') + i, j = tensor_indices('i,j', Lorentz) + ih, jh = tensor_indices('ih,jh', LorentzH) + TensorManager.set_comm(2, 3, 0) + p, q = tensorhead('p q', [Lorentz], [[1]]) + ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) + G = tensorhead('G', [Lorentz], [[1]], 2) + GH = tensorhead('GH', [LorentzH], [[1]], 3) + TensorManager.set_comm(2, 3, 0) + ps = G(i)*p(-i) + psh = GH(ih)*ph(-ih) + t = ps + psh + t1 = t*t + assert t1 == ps*ps + 2*ps*psh + psh*psh + qs = G(i)*q(-i) + qsh = GH(ih)*qh(-ih) + assert ps*qsh == qsh*ps + assert ps*qs != qs*ps From 518c757dcb24702fb3001418b3a98c20a4c8cd2d Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 8 Jan 2013 19:31:08 +0100 Subject: [PATCH 31/47] TensorManager with symbols --- sympy/tensor/dgamma_matr.py | 7 ++++-- sympy/tensor/tensor.py | 41 +++++++++++++++---------------- sympy/tensor/tests/test_tensor.py | 19 ++++++++++++++ 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index 5def70fb91ef..5868f8cc30a2 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -39,6 +39,8 @@ def __init__(self, typ, gctr=4, g5c=I): with Lorentz signature `g5c = I`, which is the default; in Euclidean space `g5c = 1` + The attribute `Gsymbol` can be used to set commutation relations + with other tensors using ``TensorManager``. """ self.g = typ.metric if not typ.dim: @@ -47,10 +49,11 @@ def __init__(self, typ, gctr=4, g5c=I): self.typ = typ sym1 = TensorSymmetry(get_symmetric_group_sgs(1)) S1 = TensorType([typ], sym1) - self.G = S1('G', 2) + self.Gsymbol = Symbol('Gsymbol') + self.G = S1('G', self.Gsymbol) sym0 = TensorSymmetry(([], [Permutation(1)])) S0 = TensorType([], sym0) - self.Gamma5 = S0('G5', 2) + self.Gamma5 = S0('G5', self.Gsymbol) self.G5 = TensMul(S.One, [self.Gamma5], [], []) self.g5c = g5c self.epsilon = typ.epsilon diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index ba34493c42e4..580ff57e81b9 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -57,17 +57,20 @@ from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs class _TensorManager(object): - def __init__(self, comm=[defaultdict(int) for i in range(2)]): - self._comm = comm - for i in range(len(self._comm)): - self._comm[0][i] = 0 - self._comm[1][0] = 0 - self._comm[1][1] = 0 + def __init__(self): + self._comm = defaultdict(dict) + self._comm[0][0] = 0 + self._comm[0][1] = 0 + self._comm[1][0] = 1 def set_comm(self, i, j, c): """ set the commutation parameter ``c`` for commutation groups ``i, j`` + ``i, j`` they can be symbols or numbers, apart from ``0`` and ``1`` + which are reserved respectively for commuting and anticommuting + tensors + ``c`` commutation number 0 commuting 1 anticommuting @@ -98,15 +101,15 @@ def set_comm(self, i, j, c): >>> (G(i1)*A(i0)).canon_bp() A(i0)*G(i1) """ - assert isinstance(i, int) and isinstance(j, int) and i >= 2 and j >= 2 - assert c is None or isinstance(c, int) and c >= 0 - m = max(i,j) + 1 - if len(self._comm) < m: - self._comm.extend([{} for k in range(m - len(self._comm))]) - if not self._comm[i]: + if i not in self._comm.keys(): self._comm[i][0] = 0 - self._comm[i][j] = c - self._comm[j][i] = c + self._comm[0][i] = 0 + if j not in self._comm.keys(): + self._comm[0][j] = 0 + if j not in self._comm[i]: + self._comm[i][j] = c + if i not in self._comm[j]: + self._comm[j][i] = c def set_comms(self, *args): for i, j, c in range(len(args)): @@ -118,12 +121,9 @@ def get_comm(self, i, j): see ``_TensorManager.set_comm`` """ - try: - return self._comm[i].get(j, 0 if i*j == 0 else None) - except IndexError: - if j == 0: - return 0 - return None + if i == 0 or j == 0: + return 0 + return self._comm[i].get(j) def clear(self): @@ -1540,7 +1540,6 @@ def fun_eval(self, *index_tuples): >>> t.substitute_indices((i,j), (j, k)) A(j, L_0)*B(-L_0, -k) """ - #fixed = kwargs.pop('fixed', False) free = self._free free1 = [] for j, ipos, cpos in free: diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 89b2cf59b928..32ec57017733 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -866,3 +866,22 @@ def test_TensorManager(): qsh = GH(ih)*qh(-ih) assert ps*qsh == qsh*ps assert ps*qs != qs*ps + + TensorManager.clear() + # same as before, but using symbols in TensorManager + Gsymbol = Symbol('Gsymbol') + GHsymbol = Symbol('GHsymbol') + p, q = tensorhead('p q', [Lorentz], [[1]]) + ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) + G = tensorhead('G', [Lorentz], [[1]], Gsymbol) + GH = tensorhead('GH', [LorentzH], [[1]], GHsymbol) + TensorManager.set_comm(Gsymbol, GHsymbol, 0) + ps = G(i)*p(-i) + psh = GH(ih)*ph(-ih) + t = ps + psh + t1 = t*t + assert t1 == ps*ps + 2*ps*psh + psh*psh + qs = G(i)*q(-i) + qsh = GH(ih)*qh(-ih) + assert ps*qsh == qsh*ps + assert ps*qs != qs*ps From 75b32046dd1f29adf1d04167ef6bacdeb4dcf672 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 8 Jan 2013 22:51:09 +0100 Subject: [PATCH 32/47] changed _TensorManager --- sympy/tensor/tensor.py | 55 +++++++++++++++++++++++-------- sympy/tensor/tests/test_tensor.py | 4 ++- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 580ff57e81b9..1c35bbdcb2c6 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -62,6 +62,25 @@ def __init__(self): self._comm[0][0] = 0 self._comm[0][1] = 0 self._comm[1][0] = 1 + self._comm_symbols2i = {0:0, 1:1} + self._comm_i2symbol = {0:0, 1:1} + + def comm_symbols2i(self, i): + """ + get the commutation group number corresponding to ``i`` + + ``i`` can be a symbol or a number + """ + if i not in self._comm_symbols2i: + n = len(self._comm_symbols2i) + self._comm[n][0] = 0 + self._comm[0][n] = 0 + self._comm_symbols2i[i] = n + return n + return self._comm_symbols2i[i] + + def comm_i2symbol(self, i): + return self._comm_i2symbol[i] def set_comm(self, i, j, c): """ @@ -101,15 +120,23 @@ def set_comm(self, i, j, c): >>> (G(i1)*A(i0)).canon_bp() A(i0)*G(i1) """ - if i not in self._comm.keys(): - self._comm[i][0] = 0 - self._comm[0][i] = 0 + def set_comm(self, i, j, c): + if i not in self._comm_symbols2i: + n = len(self._comm_symbols2i) + self._comm[n][0] = 0 + self._comm[0][n] = 0 + self._comm_symbols2i[i] = n + self._comm_i2symbol[n] = i if j not in self._comm.keys(): - self._comm[0][j] = 0 - if j not in self._comm[i]: - self._comm[i][j] = c - if i not in self._comm[j]: - self._comm[j][i] = c + n = len(self._comm_symbols2i) + self._comm[0][n] = 0 + self._comm[n][0] = 0 + self._comm_symbols2i[j] = n + self._comm_i2symbol[n] = j + ni = self._comm_symbols2i[i] + nj = self._comm_symbols2i[j] + self._comm[ni][nj] = c + self._comm[nj][ni] = c def set_comms(self, *args): for i, j, c in range(len(args)): @@ -121,10 +148,12 @@ def get_comm(self, i, j): see ``_TensorManager.set_comm`` """ - if i == 0 or j == 0: - return 0 - return self._comm[i].get(j) - + try: + return self._comm[i].get(j, 0 if i*j == 0 else None) + except IndexError: + if j == 0: + return 0 + return None def clear(self): for i in range(2, len(self._comm)): @@ -532,7 +561,7 @@ def __new__(cls, name, typ, comm, **kw_args): obj.rank = len(obj.index_types) obj.types = typ.types obj.symmetry = typ.symmetry - obj.comm = comm + obj.comm = TensorManager.comm_symbols2i(comm) return obj name = property(lambda self: self.args[0]) diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 32ec57017733..7e96b52a061a 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -291,6 +291,7 @@ def test_canonicalize1(): Gamma = S1('Gamma', 2) Gamma2 = S2a('Gamma', 2) Gamma3 = S3a('Gamma', 2) + TensorManager.set_comm(2,2,None) t = Gamma2(-mu,-nu)*Gamma(rho)*Gamma3(nu, mu, alpha) tc = t.canon_bp() assert str(tc) == '-Gamma(L_0, L_1)*Gamma(rho)*Gamma(alpha, -L_0, -L_1)' @@ -871,11 +872,12 @@ def test_TensorManager(): # same as before, but using symbols in TensorManager Gsymbol = Symbol('Gsymbol') GHsymbol = Symbol('GHsymbol') + TensorManager.set_comm(Gsymbol, GHsymbol, 0) p, q = tensorhead('p q', [Lorentz], [[1]]) ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) G = tensorhead('G', [Lorentz], [[1]], Gsymbol) + assert TensorManager._comm_i2symbol[G.comm] == Gsymbol GH = tensorhead('GH', [LorentzH], [[1]], GHsymbol) - TensorManager.set_comm(Gsymbol, GHsymbol, 0) ps = G(i)*p(-i) psh = GH(ih)*ph(-ih) t = ps + psh From 5f2a832bee28adf1a068514757568951cc57ec41 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Wed, 9 Jan 2013 13:26:40 +0100 Subject: [PATCH 33/47] Added _hashable_content --- sympy/tensor/tensor.py | 34 +++++++++++++++++++++---------- sympy/tensor/tests/test_tensor.py | 13 ++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 1c35bbdcb2c6..5a48abae37d7 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -50,7 +50,6 @@ """ - from collections import defaultdict from sympy.core import Basic, sympify, Add, Mul, S from sympy.core.symbol import Symbol, symbols @@ -120,7 +119,6 @@ def set_comm(self, i, j, c): >>> (G(i1)*A(i0)).canon_bp() A(i0)*G(i1) """ - def set_comm(self, i, j, c): if i not in self._comm_symbols2i: n = len(self._comm_symbols2i) self._comm[n][0] = 0 @@ -161,7 +159,6 @@ def clear(self): TensorManager = _TensorManager() - class TensorIndexType(Basic): """ A TensorIndexType is characterized by its name and its metric. @@ -175,7 +172,7 @@ class TensorIndexType(Basic): In the case of antisymmetric metric, the following raising and lowering conventions will be adopted: - ``psi(a) = g(a, b)*psi(-b); chi(-a) = chi(b)*g(-b, -a) + ``psi(a) = g(a, b)*psi(-b); chi(-a) = chi(b)*g(-b, -a)`` ``g(-a, b) = delta(-a, b); g(b, -a) = -delta(a, -b)`` @@ -194,7 +191,7 @@ class TensorIndexType(Basic): def __new__(cls, name, metric=False, dim=None, eps_dim = None, dummy_fmt=None): """ - name name of the tensor type + ``name`` name of the tensor type ``metric``: it can be True, False, None or another object If it is True, False, None it gives its antisymmetry: @@ -345,7 +342,7 @@ class TensorSymmetry(Basic): """ Symmetry of a tensor - bsgs tuple (base, sgs) BSGS of the symmetry of the tensor + ``bsgs`` tuple (base, sgs) BSGS of the symmetry of the tensor Examples ======== @@ -367,6 +364,10 @@ def __new__(cls, bsgs, **kw_args): generators = property(lambda self: self.args[1]) rank = property(lambda self: self.args[1][0].size) + def _hashable_content(self): + r = (tuple(self.base), tuple(self.generators)) + return r + def tensorsymmetry(*args): """ return a ``TensorSymmetry`` object @@ -390,8 +391,6 @@ def tensorsymmetry(*args): Examples ======== - Symmetric tensor using a BSBS - Symmetric tensor using a Young tableau >>> from sympy.tensor.tensor import TensorIndexType, TensorType, tensorsymmetry @@ -423,7 +422,6 @@ def tableau2bsgs(a): raise NotImplementedError return bsgs - if not args: return TensorSymmetry([[], [Permutation(2)]]) if len(args) == 2 and isinstance(args[1][0], Permutation): @@ -516,6 +514,7 @@ def tensorhead(name, typ, sym, comm=0): ``comm``: commutation group number see ``_TensorManager.set_comm`` + Examples ======== @@ -546,6 +545,7 @@ def __new__(cls, name, typ, comm, **kw_args): ``comm`` commutation group number see ``_TensorManager.set_comm`` + Examples ======== @@ -570,6 +570,10 @@ def __new__(cls, name, typ, comm, **kw_args): def __lt__(self, other): return (self.name, self.index_types) < (other.name, other.index_types) + def _hashable_content(self): + r = (self.name, tuple(self.types), self.symmetry, self.comm) + return r + def commutes_with(self, other): """ Returns 0 (1) if self and other (anti)commute. @@ -579,6 +583,7 @@ def commutes_with(self, other): r = TensorManager.get_comm(self.comm, other.comm) return r + def __str__(self): return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) __repr__ = __str__ @@ -855,7 +860,6 @@ def __new__(cls, *args, **kw_args): # collect canonicalized terms args.sort(key=lambda x: (x._components, x._free, x._dum)) a = _tensAdd_collect_terms(args) - if not a: return S.Zero # it there is only a component tensor return it @@ -917,7 +921,6 @@ def __eq__(self, other): if isinstance(other, TensExpr) and other.is_TensMul and other._coeff == 0: return self == 0 t = self - other - if not isinstance(t, TensExpr): return t == 0 else: @@ -926,6 +929,9 @@ def __eq__(self, other): else: return all(x._coeff == 0 for x in t.args) + def _hashable_content(self): + return tuple(self.args) + def __ne__(self, other): return not (self == other) @@ -1048,6 +1054,12 @@ def __eq__(self, other): res = self - other return res == 0 + def _hashable_content(self): + t = self.canon_bp() + r = (t._coeff, tuple(t._components), \ + tuple(sorted(t._free)), tuple(sorted(t._dum))) + return r + def __ne__(self, other): return not self == other diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 7e96b52a061a..131511bf5147 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -887,3 +887,16 @@ def test_TensorManager(): qsh = GH(ih)*qh(-ih) assert ps*qsh == qsh*ps assert ps*qs != qs*ps + +def test_hash(): + D = Symbol('D') + Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + a,b,c,d,e = tensor_indices('a,b,c,d,e', Lorentz) + g = Lorentz.metric + + p, q = tensorhead('p q', [Lorentz], [[1]]) + t1 = p(a)*q(b) + t2 = p(a)*p(b) + t3 = p(a)*p(b) + g(a,b) + d = {t1:1, t2:2, t3:3} + assert d[t1] == 1 and d[t2] == 2 and d[t3] == 3 From c3cc22771422c65b349f8bde7996c1e8f2e7e203 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Wed, 9 Jan 2013 15:36:22 +0100 Subject: [PATCH 34/47] added __hash__ to fix bug in Python3 --- sympy/tensor/tensor.py | 12 ++++++++++++ sympy/tensor/tests/test_tensor.py | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 5a48abae37d7..5fc2aa12efd1 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -368,6 +368,9 @@ def _hashable_content(self): r = (tuple(self.base), tuple(self.generators)) return r + def __hash__(self): + return Basic.__hash__(self) + def tensorsymmetry(*args): """ return a ``TensorSymmetry`` object @@ -574,6 +577,9 @@ def _hashable_content(self): r = (self.name, tuple(self.types), self.symmetry, self.comm) return r + def __hash__(self): + return Basic.__hash__(self) + def commutes_with(self, other): """ Returns 0 (1) if self and other (anti)commute. @@ -932,6 +938,9 @@ def __eq__(self, other): def _hashable_content(self): return tuple(self.args) + def __hash__(self): + return Basic.__hash__(self) + def __ne__(self, other): return not (self == other) @@ -1060,6 +1069,9 @@ def _hashable_content(self): tuple(sorted(t._free)), tuple(sorted(t._dum))) return r + def __hash__(self): + return Basic.__hash__(self) + def __ne__(self, other): return not self == other diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 131511bf5147..abf26b077bb6 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -291,7 +291,6 @@ def test_canonicalize1(): Gamma = S1('Gamma', 2) Gamma2 = S2a('Gamma', 2) Gamma3 = S3a('Gamma', 2) - TensorManager.set_comm(2,2,None) t = Gamma2(-mu,-nu)*Gamma(rho)*Gamma3(nu, mu, alpha) tc = t.canon_bp() assert str(tc) == '-Gamma(L_0, L_1)*Gamma(rho)*Gamma(alpha, -L_0, -L_1)' @@ -897,6 +896,7 @@ def test_hash(): p, q = tensorhead('p q', [Lorentz], [[1]]) t1 = p(a)*q(b) t2 = p(a)*p(b) + assert hash(t1) != hash(t2) t3 = p(a)*p(b) + g(a,b) - d = {t1:1, t2:2, t3:3} - assert d[t1] == 1 and d[t2] == 2 and d[t3] == 3 + t4 = p(a)*p(b) - g(a,b) + assert hash(t3) != hash(t4) From e7ffa036d66809acf53dd50b65f316d84483201f Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 10 Jan 2013 19:14:05 +0100 Subject: [PATCH 35/47] HoldTensorHead to hold a tensor in non-distributed form Added default noncommuting group in _TensorManager --- sympy/tensor/tensor.py | 124 +++++++++++++++++++++++++++--- sympy/tensor/tests/test_tensor.py | 26 +++++-- 2 files changed, 133 insertions(+), 17 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 5fc2aa12efd1..117beedfda69 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -61,8 +61,12 @@ def __init__(self): self._comm[0][0] = 0 self._comm[0][1] = 0 self._comm[1][0] = 1 - self._comm_symbols2i = {0:0, 1:1} - self._comm_i2symbol = {0:0, 1:1} + self._comm[2][0] = 1 + self._comm[0][2] = 1 + self._comm[2][1] = None + self._comm[1][2] = None + self._comm_symbols2i = {0:0, 1:1, 2:2} + self._comm_i2symbol = {0:0, 1:1, 2:2} def comm_symbols2i(self, i): """ @@ -85,9 +89,10 @@ def set_comm(self, i, j, c): """ set the commutation parameter ``c`` for commutation groups ``i, j`` - ``i, j`` they can be symbols or numbers, apart from ``0`` and ``1`` - which are reserved respectively for commuting and anticommuting - tensors + ``i, j`` they can be symbols or numbers, apart from ``0, 1`` and ``2`` + which are reserved respectively for commuting, anticommuting + tensors and tensors not commuting with any other group apart with + the commuting tensors. ``c`` commutation number 0 commuting @@ -96,6 +101,8 @@ def set_comm(self, i, j, c): Tensors in the group ``i=0`` commute with any other tensor. Tensors in the group ``i=1`` anticommute within that group. + Tensors in the group ``i=2`` commute only with the ``0`` group. + For the remaining cases, use this method to set the commutation rules; by default ``c=None``. @@ -109,9 +116,9 @@ def set_comm(self, i, j, c): >>> Lorentz = TensorIndexType('Lorentz') >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) >>> A = tensorhead('A', [Lorentz], [[1]]) - >>> G = tensorhead('G', [Lorentz], [[1]], 2) - >>> GH = tensorhead('GH', [Lorentz], [[1]], 3) - >>> TensorManager.set_comm(2, 3, 0) + >>> G = tensorhead('G', [Lorentz], [[1]], 3) + >>> GH = tensorhead('GH', [Lorentz], [[1]], 4) + >>> TensorManager.set_comm(3, 4, 0) >>> (GH(i1)*G(i0)).canon_bp() G(i0)*GH(i1) >>> (G(i1)*G(i0)).canon_bp() @@ -615,6 +622,62 @@ def __call__(self, *indices): dum.sort() return TensMul(S.One, components, free, dum) +class HoldTensorHead(TensorHead): + def __new__(cls, t, **kw_args): + """ + + ``t`` tensor expression + + Keyword arguments: + ``symmetry`` symmetry of the tensor expression; + if not assigned no symmetry is assumed + + ``comm`` commutation group; + if not assigned it is assumed to be not commuting + + Examples + ==== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead, HoldTensorHead + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> a,b,c,d = tensor_indices('a,b,c,d', Lorentz) + >>> p, q = tensorhead('p,q', [Lorentz], [[1]]) + >>> h1 = HoldTensorHead(p(a)*q(b) - p(b)*q(a), symm=[[2]], comm=0) + >>> h2 = HoldTensorHead(p(a)*q(b) - p(b)*q(a), symm=[[1]*2], comm=0) + >>> (h1(a,b)*h2(-a,-b)).canon_bp() + 0 + >>> t = h1(a,b)*h1(-a,-b) + >>> t.expand(True) - 2*p(a)*p(-a)*q(b)*q(-b) + 2*p(a)*q(-a)*q(b)*p(-b) + 0 + """ + types = [x.tensortype for x in t.free_args] + rank = len(t.free_args) + symmetry = kw_args.get('symm', [[1]]*rank) + if isinstance(symmetry, list): + symmetry = tensorsymmetry(*symmetry) + comm = kw_args.get('comm') + if not comm: + comm = 2 + else: + comm = TensorManager.comm_symbols2i(comm) + name = str(t) + typ = TensorType(types, symmetry) + obj = Basic.__new__(cls, name, typ, t) + obj.free_args = t.free_args + obj.comm = comm + obj.symmetry = symmetry + obj.types = types + obj.rank = rank + return obj + + name = property(lambda self: self.args[0]) + t = property(lambda self: self.args[2]) + + + def call(self, *indices): + t = self.t.substitute_indices(*zip(self.free_args, indices)) + return t + class TensExpr(Basic): """ @@ -1003,6 +1066,13 @@ def expand_coeff(self): args = [_tensor_expand_coeff(x) for x in self.args] return TensAdd(*args) + def expand(self, expand_all=False): + """ + expand HoldTensorHead tensors + """ + a = [x.expand(expand_all) for x in self.args] + return TensAdd(*a) + def _pretty(self): a = [] args = self.args @@ -1075,6 +1145,31 @@ def __hash__(self): def __ne__(self, other): return not self == other + def expand(self, expand_all=False): + """ + expand HoldTensorHead tensors + """ + if len(self._components) == 1: + if isinstance(self._components[0], HoldTensorHead): + return self._components[0].call(*self.free_args) + else: + return self + a = self.split() + if expand_all: + a1 = [x.expand(expand_all) for x in a] + else: + a1 = [] + expanded = False + for i, t in enumerate(a): + t1 = t.expand() + if t1 is not t: + expanded = True + a1.append(t1) + if expanded is True: + break + a1 += a[i + 1:] + return tensor_mul(*a1) + @staticmethod def from_indices(*indices): """ @@ -1647,14 +1742,19 @@ def expand_coeff(self): def _pretty(self): if self._components == []: return str(self._coeff) - indices = [str(ind) for ind in self.get_indices()] + indices = self.get_indices() + sindices = [str(ind) for ind in indices] pos = 0 a = [] for t in self._components: - if t.rank > 0: - a.append('%s(%s)' % (t.name, ', '.join(indices[pos:pos + t.rank]))) + if isinstance(t, HoldTensorHead): + rt = '(%s)' % (t.call(*indices[pos:pos + t.rank])) + a.append(rt) else: - a.append('%s' % t.name) + if t.rank > 0: + a.append('%s(%s)' % (t.name, ', '.join(sindices[pos:pos + t.rank]))) + else: + a.append('%s' % t.name) pos += t.rank res = '*'. join(a) if self._coeff == S.One: diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index abf26b077bb6..5c5cd6e50607 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -4,7 +4,8 @@ from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, - tensorlist_contract_metric, TensMul, tensorsymmetry, tensorhead, TensorManager) + tensorlist_contract_metric, TensMul, tensorsymmetry, tensorhead, + TensorManager, HoldTensorHead) from sympy.utilities.pytest import raises #################### Tests from tensor_can.py ####################### @@ -851,12 +852,11 @@ def test_TensorManager(): LorentzH = TensorIndexType('LorentzH', dummy_fmt='LH') i, j = tensor_indices('i,j', Lorentz) ih, jh = tensor_indices('ih,jh', LorentzH) - TensorManager.set_comm(2, 3, 0) + TensorManager.set_comm(3, 4, 0) p, q = tensorhead('p q', [Lorentz], [[1]]) ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) - G = tensorhead('G', [Lorentz], [[1]], 2) - GH = tensorhead('GH', [LorentzH], [[1]], 3) - TensorManager.set_comm(2, 3, 0) + G = tensorhead('G', [Lorentz], [[1]], 3) + GH = tensorhead('GH', [LorentzH], [[1]], 4) ps = G(i)*p(-i) psh = GH(ih)*ph(-ih) t = ps + psh @@ -900,3 +900,19 @@ def test_hash(): t3 = p(a)*p(b) + g(a,b) t4 = p(a)*p(b) - g(a,b) assert hash(t3) != hash(t4) + +def test_hold1(): + D = Symbol('D') + Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') + a,b,c,d = tensor_indices('a,b,c,d', Lorentz) + g = Lorentz.metric + p, q, r, s = tensorhead('p,q,r,s', [Lorentz], [[1]]) + t = p(a)*q(b)*r(c) + t1 = t(a,b,c) + t(a,c,b) + t(b,a,c) + t(b,c,a) + t(c,a,b) + t(c,b,a) + h = HoldTensorHead(t1, symmetry=tensorsymmetry([1]*3), comm=0) + t2 = t1(a,b,c)*t1(-a,-b,-c) + # this is faster + t2a = h(a,b,c)*h(-a,-b,-c) + t2a = t2a.expand() + t2a = t2a.expand() + assert t2 == t2a From 08de0cc93102610c165a33f80b827d721c4366c2 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 10 Jan 2013 23:15:44 +0100 Subject: [PATCH 36/47] updated to PR 1700 --- sympy/tensor/tensor.py | 156 ++++++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 72 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 117beedfda69..d70a13467870 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -26,27 +26,6 @@ If there is a (anti)symmetric metric, the indices can be raised and lowered when the tensor is put in canonical form. - - - -TODO: - -* better integration in SymPy - -* term collection - -* introduce scalars: -for example in ``p(a)*p(b)/(p**2 + m**2)`` the scalar ``p**2`` -is equivalent to the tensor ``p(c)*p(-c)``, but can appear in the denominator. - -* introduce tensor symmetrizers and algebraic operations on them - -* Rule for contraction of Levi-Civita ``epsilon`` tensors: -one must intoduce generalized Kronecker deltas to do this properly - -* develop a Young tableaux model: -one of its uses is in ``tensorsymmetry`` to provide the symmetry of the tensor -using the Young diagrams """ @@ -249,8 +228,9 @@ def __new__(cls, name, metric=False, dim=None, eps_dim = None, obj.epsilon = obj.get_epsilon() return obj - name = property(lambda self: self.args[0]) - + @property + def name(self): + return self.args[0] def get_kronecker_delta(self): sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) @@ -367,9 +347,17 @@ def __new__(cls, bsgs, **kw_args): obj = Basic.__new__(cls, base, generators, **kw_args) return obj - base = property(lambda self: self.args[0]) - generators = property(lambda self: self.args[1]) - rank = property(lambda self: self.args[1][0].size) + @property + def base(self): + return self.args[0] + + @property + def generators(self): + return self.args[1] + + @property + def rank(self): + return self.args[1][0].size def _hashable_content(self): r = (tuple(self.base), tuple(self.generators)) @@ -465,11 +453,17 @@ def __new__(cls, index_types, symmetry, **kw_args): obj = Basic.__new__(cls, index_types, symmetry, **kw_args) return obj - index_types = property(lambda self: self.args[0]) + @property + def index_types(self): + return self.args[0] - symmetry = property(lambda self: self.args[1]) + @property + def symmetry(self): + return self.args[1] - types = property(lambda self: sorted(set(self.index_types), key=lambda x: x.name)) + @property + def types(self): + return sorted(set(self.index_types), key=lambda x: x.name) def __str__(self): return 'TensorType(%s)' %([str(x) for x in self.index_types]) @@ -574,8 +568,13 @@ def __new__(cls, name, typ, comm, **kw_args): obj.comm = TensorManager.comm_symbols2i(comm) return obj - name = property(lambda self: self.args[0]) - index_types = property(lambda self: self.args[1].index_types) + @property + def name(self): + return self.args[0] + + @property + def index_types(self): + return self.args[1].index_types def __lt__(self, other): return (self.name, self.index_types) < (other.name, other.index_types) @@ -762,47 +761,6 @@ def __rdiv__(self, other): __truediv__ = __div__ __rtruediv__ = __rdiv__ - def substitute_indices(self, *index_tuples): - """ - Return a tensor with free indices substituted according to ``index_tuples`` - - ``index_types`` list of tuples ``(old_index, new_index)`` - - Examples - ======== - - >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead - >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) - >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) - >>> t = A(i, k)*B(-k, -j); t - A(i, L_0)*B(-L_0, -j) - >>> t.substitute_indices((i,j), (j, k)) - A(j, L_0)*B(-L_0, -k) - """ - if self.is_TensMul: - free = self._free - free1 = [] - for j, ipos, cpos in free: - for i, v in index_tuples: - if i.name == j.name and i.tensortype == j.tensortype: - if i.is_up == j.is_up: - free1.append((v, ipos, cpos)) - else: - free1.append((-v, ipos, cpos)) - break - else: - free1.append((j, ipos, cpos)) - - return TensMul(self._coeff, self._components, free1, self._dum) - if self.is_TensAdd: - args = self.args - args1 = [] - for x in args: - y = x.substitute_indices(*index_tuples) - args1.append(y) - return TensAdd(*args1) - def _tensAdd_collect_terms(args): """ @@ -1073,6 +1031,27 @@ def expand(self, expand_all=False): a = [x.expand(expand_all) for x in self.args] return TensAdd(*a) + def substitute_indices(self, *index_tuples): + """ + Return a tensor with free indices substituted according to ``index_tuples`` + + ``index_types`` list of tuples ``(old_index, new_index)`` + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) + """ + args = self.args + args1 = [] + for x in args: + y = x.substitute_indices(*index_tuples) + args1.append(y) + return TensAdd(*args1) + def _pretty(self): a = [] args = self.args @@ -1625,6 +1604,39 @@ def contract_metric(self, g, contract_all=False): raise NotImplementedError return self._contract(g, g.index_types[0].metric_antisym, contract_all) + def substitute_indices(self, *index_tuples): + """ + Return a tensor with free indices substituted according to ``index_tuples`` + + ``index_types`` list of tuples ``(old_index, new_index)`` + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensorhead + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) + >>> t = A(i, k)*B(-k, -j); t + A(i, L_0)*B(-L_0, -j) + >>> t.substitute_indices((i,j), (j, k)) + A(j, L_0)*B(-L_0, -k) + """ + free = self._free + free1 = [] + for j, ipos, cpos in free: + for i, v in index_tuples: + if i.name == j.name and i.tensortype == j.tensortype: + if i.is_up == j.is_up: + free1.append((v, ipos, cpos)) + else: + free1.append((-v, ipos, cpos)) + break + else: + free1.append((j, ipos, cpos)) + + return TensMul(self._coeff, self._components, free1, self._dum) + def substitute_tensor(self, t1, t2, subst_all=False): """ Return tensor obtained substituting ``t1`` with ``t2`` in ``self``. From b80e8610377a4c0368064665d9ebeaecda59b841 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Thu, 10 Jan 2013 23:25:27 +0100 Subject: [PATCH 37/47] eliminated is_TensAdd and is_TensMul --- sympy/tensor/dgamma_matr.py | 12 ++++++------ sympy/tensor/tensor.py | 15 +++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/sympy/tensor/dgamma_matr.py b/sympy/tensor/dgamma_matr.py index 5868f8cc30a2..52a6b1ccfea4 100644 --- a/sympy/tensor/dgamma_matr.py +++ b/sympy/tensor/dgamma_matr.py @@ -66,7 +66,7 @@ def G5_to_right(self, t): """ if not isinstance(t, TensExpr): return t - if t.is_TensAdd: + if isinstance(t, TensAdd): a = [self.G5_to_right(x) for x in t.args] return TensAdd(*a) components = t._components @@ -224,7 +224,7 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): """ if not isinstance(t, TensExpr): return t - if t.is_TensMul: + if isinstance(t, TensMul): for n in range(nmax + 1): r = self.match1_gamma(t, n) if not r: @@ -236,7 +236,7 @@ def do_rule1_gamma(self, t, nmax=4, doall=False): t1 = self.do_rule1_gamma(t1, nmax, doall) return t1 return t - if t.is_TensAdd: + if isinstance(t, TensAdd): a = [self.do_rule1_gamma(tx, nmax, doall) for tx in t.args] t1 = TensAdd(*a) return t1.contract_metric(self.g, contract_all=True) @@ -359,10 +359,10 @@ def do_rule2_gamma(self, t, nmax=1): """ if not isinstance(t, TensExpr): return t - if t.is_TensMul: + if isinstance(t, TensMul): for n in range(nmax + 1): t = self.rule2_gamma(t, n) - if t.is_TensAdd: + if isinstance(t, TensAdd): a = [self.do_rule2_gamma(tx) for tx in t.args] t = TensAdd(*a) return t @@ -398,7 +398,7 @@ def gamma_trace(self, t): if not isinstance(t, TensExpr): return self.gctr*t t = self.G5_to_right(t) - if t.is_TensAdd: + if isinstance(t, TensAdd): a = [self.gamma_trace(x) for x in t.args] return TensAdd(*a) components = t._components diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index d70a13467870..d0050aa0c057 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -701,7 +701,6 @@ class TensExpr(Basic): """ _op_priority = 11.0 - is_TensMul = False is_TensAdd = False is_commutative = False @@ -713,7 +712,7 @@ def __abs__(self): def __add__(self, other): other = sympify(other) - if self.is_TensAdd: + if isinstance(self, TensAdd): args = self.args + (other,) return TensAdd(*args) return TensAdd(self, other) @@ -729,14 +728,14 @@ def __rsub__(self, other): return TensAdd(other, -self) def __mul__(self, other): - if self.is_TensAdd: + if isinstance(self, TensAdd): return TensAdd(*[x*other for x in self.args]) if other.is_TensAdd: return TensAdd(*[self*x for x in other.args]) return TensMul.__mul__(self, other) def __rmul__(self, other): - if self.is_TensMul: + if isinstance(self, TensMul): coeff = other*self._coeff return TensMul(coeff, self._components, self._free, self._dum) return TensAdd(*[x*other for x in self.args]) @@ -876,7 +875,7 @@ def __new__(cls, *args, **kw_args): _tensAdd_check(args) obj = Basic.__new__(cls, **kw_args) - if len(args) == 1 and args[0].is_TensMul: + if len(args) == 1 and isinstance(args[0], TensMul): obj._args = tuple(args) return obj args = [x.canon_bp() for x in args if x] @@ -945,13 +944,14 @@ def __eq__(self, other): if not isinstance(other, TensExpr): if len(self.args) == 1: return self.args[0]._coeff == other - if isinstance(other, TensExpr) and other.is_TensMul and other._coeff == 0: + if isinstance(other, TensExpr) and isinstance(other, TensMul ) \ + and other._coeff == 0: return self == 0 t = self - other if not isinstance(t, TensExpr): return t == 0 else: - if t.is_TensMul: + if isinstance(t, TensMul): return t._coeff == 0 else: return all(x._coeff == 0 for x in t.args) @@ -1066,7 +1066,6 @@ class TensMul(TensExpr): """ Product of tensors """ - is_TensMul = True def __new__(cls, coeff, *args, **kw_args): """ From fe70affec1b91f65f2cd2e393c97a24c9df09761 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Fri, 11 Jan 2013 09:36:05 +0100 Subject: [PATCH 38/47] eliminated forgotten is_TensAdd; eliminated isinstance calls in TensExpr --- sympy/tensor/group_factors.py | 8 ++--- sympy/tensor/tensor.py | 57 +++++++++++++++++-------------- sympy/tensor/tests/test_tensor.py | 4 +++ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/sympy/tensor/group_factors.py b/sympy/tensor/group_factors.py index a057ba65cee5..79c24441848a 100644 --- a/sympy/tensor/group_factors.py +++ b/sympy/tensor/group_factors.py @@ -158,7 +158,7 @@ def rule_C_triangle(self, t): """ evaluate a triangle of C's """ - if t.is_TensAdd: + if isinstance(t, TensAdd): args = t.args args = [self.rule_C_triangle(x) for x in args] return TensAdd(*args) @@ -184,7 +184,7 @@ def rule_C_square(self, t): """ evaluate a square of C's """ - if t.is_TensAdd: + if isinstance(t, TensAdd): args = t.args args = [self.rule_C_square(x) for x in args] return TensAdd(*args) @@ -212,7 +212,7 @@ def rule_thetaii(self, t): theta(i_0,..,i_m)*theta(j_0,..,j_n) - 1/N*theta(i_0,..,i_m,j_0,..,j_n) """ - if t.is_TensAdd: + if isinstance(t, TensAdd): args = t.args args = [self.rule_thetaii(x) for x in args] return TensAdd(*args) @@ -249,7 +249,7 @@ def rule_thetaithetai(self, t): theta(k,i_0,..,i_m)*theta(k,j_0,...,j_n) = theta(i_0,...,i_m,j_0,...,j_n) - 1/N*theta(i_0,..,i_m)*theta(j_0,...,j_n) """ - if t.is_TensAdd: + if isinstance(t, TensAdd): args = t.args args = [self.rule_thetaithetai(x) for x in args] return TensAdd(*args) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index d0050aa0c057..63bf17372cee 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -140,7 +140,7 @@ def get_comm(self, i, j): return None def clear(self): - for i in range(2, len(self._comm)): + for i in range(3, len(self._comm)): self._comm[i].clear() TensorManager = _TensorManager() @@ -701,7 +701,6 @@ class TensExpr(Basic): """ _op_priority = 11.0 - is_TensAdd = False is_commutative = False def __neg__(self): @@ -711,33 +710,21 @@ def __abs__(self): raise NotImplementedError def __add__(self, other): - other = sympify(other) - if isinstance(self, TensAdd): - args = self.args + (other,) - return TensAdd(*args) return TensAdd(self, other) def __radd__(self, other): return TensAdd(other, self) def __sub__(self, other): - other = sympify(other) return TensAdd(self, -other) def __rsub__(self, other): return TensAdd(other, -self) def __mul__(self, other): - if isinstance(self, TensAdd): - return TensAdd(*[x*other for x in self.args]) - if other.is_TensAdd: - return TensAdd(*[self*x for x in other.args]) - return TensMul.__mul__(self, other) + raise NotImplementedError def __rmul__(self, other): - if isinstance(self, TensMul): - coeff = other*self._coeff - return TensMul(coeff, self._components, self._free, self._dum) return TensAdd(*[x*other for x in self.args]) def __pow__(self, other): @@ -747,12 +734,7 @@ def __rpow__(self, other): raise NotImplementedError def __div__(self, other): - other = sympify(other) - if isinstance(other, TensExpr): - raise ValueError('cannot divide by a tensor') - coeff = self._coeff/other - return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) - + raise NotImplementedError def __rdiv__(self, other): raise NotImplementedError() @@ -812,14 +794,14 @@ def _tensAdd_flatten(args): args1 = [] for x in args: if isinstance(x, TensExpr): - if x.is_TensAdd: + if isinstance(x, TensAdd): args1.extend(list(x.args)) else: args1.append(x) args1 = [x for x in args1 if isinstance(x, TensExpr) and x._coeff] args2 = [x for x in args if not isinstance(x, TensExpr)] t0 = args1[0] - if t0.is_TensAdd: + if isinstance(t0, TensAdd): t0 = t0.args[0] if t0._free: raise ValueError('all tensors must have the same indices') @@ -827,7 +809,7 @@ def _tensAdd_flatten(args): args = [t1] + args1 a = [] for x in args: - if x.is_TensAdd: + if isinstance(x, TensAdd): a.extend(list(x.args)) else: a.append(x) @@ -862,7 +844,6 @@ class TensAdd(TensExpr): >>> t(b) p(b) + q(b) """ - is_TensAdd = True def __new__(cls, *args, **kw_args): """ @@ -956,6 +937,20 @@ def __eq__(self, other): else: return all(x._coeff == 0 for x in t.args) + def __add__(self, other): + other = sympify(other) + args = self.args + (other,) + return TensAdd(*args) + + def __mul__(self, other): + return TensAdd(*[x*other for x in self.args]) + + def __div__(self, other): + other = sympify(other) + if isinstance(other, TensExpr): + raise ValueError('cannot divide by a tensor') + return TensAdd(*[x/other for x in self.args]) + def _hashable_content(self): return tuple(self.args) @@ -1379,7 +1374,7 @@ def __mul__(self, other): if not isinstance(other, TensExpr): coeff = self._coeff*other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) - if other.is_TensAdd: + if isinstance(other, TensAdd): return TensAdd(*[self*x for x in other.args]) components = self._components + other._components @@ -1409,6 +1404,16 @@ def __mul__(self, other): coeff = self._coeff*other._coeff return TensMul(coeff, components, free, dum) + def __rmul__(self, other): + coeff = other*self._coeff + return TensMul(coeff, self._components, self._free, self._dum) + + def __div__(self, other): + other = sympify(other) + if isinstance(other, TensExpr): + raise ValueError('cannot divide by a tensor') + coeff = self._coeff/other + return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) def sorted_components(self): """ diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 5c5cd6e50607..8b39a6077dc0 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -482,6 +482,10 @@ def test_add1(): t2 = (p(j) + q(j) + 2*r(j))*(p(i) - q(i)) t = t1 + t2 assert t == 2*p(i)*p(j) + 2*p(i)*r(j) + 2*p(j)*r(i) - 2*q(i)*q(j) - 2*q(i)*r(j) - 2*q(j)*r(i) + t = p(i)*q(j)/2 + assert 2*t == p(i)*q(j) + t = (p(i) + q(i))/2 + assert 2*t == p(i) + q(i) def test_add2(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') From 7a1b54b6205f9f5bdc1b41f3f49dd46b5f0a1d9d Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Fri, 11 Jan 2013 10:02:56 +0100 Subject: [PATCH 39/47] Kept `__hash__` only for `TensAdd` and `TensMul`, used `super`. --- sympy/tensor/tensor.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 63bf17372cee..ac13a5e14044 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -363,8 +363,6 @@ def _hashable_content(self): r = (tuple(self.base), tuple(self.generators)) return r - def __hash__(self): - return Basic.__hash__(self) def tensorsymmetry(*args): """ @@ -583,9 +581,6 @@ def _hashable_content(self): r = (self.name, tuple(self.types), self.symmetry, self.comm) return r - def __hash__(self): - return Basic.__hash__(self) - def commutes_with(self, other): """ Returns 0 (1) if self and other (anti)commute. @@ -955,7 +950,7 @@ def _hashable_content(self): return tuple(self.args) def __hash__(self): - return Basic.__hash__(self) + return super(TensAdd, self).__hash__() def __ne__(self, other): return not (self == other) @@ -1113,7 +1108,7 @@ def _hashable_content(self): return r def __hash__(self): - return Basic.__hash__(self) + return super(TensMul, self).__hash__() def __ne__(self, other): return not self == other From a6fcc50b23362416d71ee7652992c1c45f6b62ad Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Fri, 11 Jan 2013 18:24:39 +0100 Subject: [PATCH 40/47] Improved the documentation; made attributes private; _pretty for TensorHead; renamed in TensMul rank ext_rank and added rank. --- sympy/printing/str.py | 3 + sympy/tensor/tensor.py | 427 +++++++++++++++++++++++++++++++---------- 2 files changed, 324 insertions(+), 106 deletions(-) diff --git a/sympy/printing/str.py b/sympy/printing/str.py index 8e89b727974d..f52a95ff6e3c 100644 --- a/sympy/printing/str.py +++ b/sympy/printing/str.py @@ -338,6 +338,9 @@ def _print_Permutation(self, expr): def _print_TensorIndex(self, expr): return expr._pretty() + def _print_TensorHead(self, expr): + return expr._pretty() + def _print_TensMul(self, expr): return expr._pretty() diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index ac13a5e14044..cc1bac373614 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -35,6 +35,20 @@ from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs class _TensorManager(object): + """ + Class to manage tensor properties. + + Notes + ===== + + Tensors belong to tensor commutation groups; each group has a label + ``comm`; there are predefined labels: + `0` tensors commuting with any other tensor + `1` tensors anticommuting among themselves + `2` tensors not commuting, apart with those with ``comm=0`` + + Other groups can be defined using `set_comm`. + """ def __init__(self): self._comm = defaultdict(dict) self._comm[0][0] = 0 @@ -149,11 +163,47 @@ class TensorIndexType(Basic): """ A TensorIndexType is characterized by its name and its metric. + Parameters + ========== + + ``name`` : name of the tensor type + + ``metric`` : metric symmetry or metric object or ``None`` + + + ``dim`` : dimension, it can be a symbol or an integer or ``None`` + + ``eps_dim`` : dimension of the epsilon tensor + + ``dummy_fmt`` : name of the head of dummy indices + + Attributes + ========== + + ``name`` + ``metric_name`` : it is 'metric' or metric.name + ``metric_antisym`` + ``metric`` : the metric tensor + ``delta`` : ``Kronecker delta`` + ``epsilon`` : the ``Levi-Civita epsilon`` tensor + ``dim`` + ``dim_eps`` + ``dummy_fmt`` + + Notes + ===== + + The ``metric`` parameter can be: ``metric = False`` symmetric metric (in Riemannian geometry) ``metric = True`` antisymmetric metric (for spinor calculus) - In these two cases the metric is used to raise and lower indices. + ``metric = None`` there is no metric + + ``metric`` can be an object having ``name`` and ``antisym`` attributes. + + + If there is a metric the metric is used to raise and lower indices. In the case of antisymmetric metric, the following raising and lowering conventions will be adopted: @@ -162,47 +212,29 @@ class TensorIndexType(Basic): ``g(-a, b) = delta(-a, b); g(b, -a) = -delta(a, -b)`` - where ``delta(-a, b) = delta(b, -a)`` is the Kronecker delta + where ``delta(-a, b) = delta(b, -a)`` is the ``Kronecker delta`` + (see ``TensorIndex`` for the conventions on indices). - ``metric = None`` there is no metric; - it is not possible to raise or lower indices; + If there is no metric it is not possible to raise or lower indices; e.g. the index of the defining representation of ``SU(N)`` is 'covariant' and the conjugate representation is 'contravariant'; for ``N > 2`` they are linearly independent. - ``metric`` can be an object having ``name`` and ``antisym`` attributes. + ``eps_dim`` is by default equal to ``dim``, if the latter is an integer; + else it can be assigned (for use in naive dimensional regularization); + if ``eps_dim`` is not an integer ``epsilon`` is ``None``. - If a dimension ``dim`` is defined, it can be a symbol or an integer. + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> Lorentz.metric + metric(Lorentz,Lorentz) """ + def __new__(cls, name, metric=False, dim=None, eps_dim = None, dummy_fmt=None): - """ - ``name`` name of the tensor type - - ``metric``: it can be True, False, None or another object - If it is True, False, None it gives its antisymmetry: - False symmetric metric - True antisymmetric - None no metric - - Otherwise, ``metric`` must have attributes ``name`` and ``antisym`` - - ``dim`` dimension, it can be a symbol or a positive integer - - ``eps_dim`` dimension of the epsilon tensor; it is by default - equal to dim, if the latter is an integer; else - it can be assigned (for use in naive dimensional - regularization) - - ``dummy_fmt`` name of the head of dummy indices; by default it is - the name of the tensor type - - Examples - ======== - - >>> from sympy.tensor.tensor import TensorIndexType - >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - """ obj = Basic.__new__(cls, name, metric) if not dummy_fmt: obj.dummy_fmt = '%s_%%d' % obj.name @@ -222,16 +254,32 @@ def __new__(cls, name, metric=False, dim=None, eps_dim = None, S2 = TensorType([obj]*2, sym2) obj.metric = S2(metric_name) - obj.dim = dim - obj.delta = obj.get_kronecker_delta() - obj.eps_dim = eps_dim if eps_dim else dim - obj.epsilon = obj.get_epsilon() + obj._dim = dim + obj._delta = obj.get_kronecker_delta() + obj._eps_dim = eps_dim if eps_dim else dim + obj._epsilon = obj.get_epsilon() return obj @property def name(self): return self.args[0] + @property + def dim(self): + return self._dim + + @property + def delta(self): + return self._delta + + @property + def eps_dim(self): + return self._eps_dim + + @property + def epsilon(self): + return self._epsilon + def get_kronecker_delta(self): sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) S2 = TensorType([self]*2, sym2) @@ -256,10 +304,26 @@ def __str__(self): class TensorIndex(Basic): """ - Tensor indices are contructed with the Einstein summation convention. + Represents an abstract tensor index. + + Parameters + ========== + + ``name`` : name of the index + ``tensortype`` : ``TensorIndexType`` of the index + ``is_up`` : flag for contravariant index + + Attributes + ========== - A TensorIndex is chacterized by their ``name``, ``tensortype`` - and ``is_up``. + ``name`` + ``tensortype`` + ``is_up`` + + Notes + ===== + + Tensor indices are contracted with the Einstein summation convention. An index can be in contravariant or in covariant form; in the latter case it is represented prepending a ``-`` to the index name. @@ -283,11 +347,20 @@ class TensorIndex(Basic): def __new__(cls, name, tensortype, is_up=True): obj = Basic.__new__(cls, name, tensortype, is_up) - obj.name = name - obj.tensortype = tensortype - obj.is_up = is_up return obj + @property + def name(self): + return self.args[0] + + @property + def tensortype(self): + return self.args[1] + + @property + def is_up(self): + return self.args[2] + def _pretty(self): s = self.name if not self.is_up: @@ -304,11 +377,14 @@ def __neg__(self): def tensor_indices(s, typ): """ - Returns list of tensor indices given their names and the type ``typ`` + Returns list of tensor indices given their names and their types + + Parameters + ========== - ``s`` string of comma separated names of indices + ``s`` : string of comma separated names of indices - ``typ`` list of TensorIndexType of the indices + ``typ`` : list of ``TensorIndexType`` of the indices Examples ======== @@ -329,7 +405,27 @@ class TensorSymmetry(Basic): """ Symmetry of a tensor - ``bsgs`` tuple (base, sgs) BSGS of the symmetry of the tensor + Parameters + ========== + + ``bsgs`` : tuple ``(base, sgs)`` BSGS of the symmetry of the tensor + + Attributes + ========== + + ``base`` : base of the BSGS + ``generators`` : generators of the BSGS + ``rank`` : rank of the tensor + + Notes + ===== + + A tensor can have an arbitrary symmetry provided by its BSGS. + + See Also + ======== + + sympy.combinatorics.tensor_can.get_symmetric_group_sgs Examples ======== @@ -366,7 +462,7 @@ def _hashable_content(self): def tensorsymmetry(*args): """ - return a ``TensorSymmetry`` object + Return a ``TensorSymmetry`` object. One can represent a tensor with any slot symmetry group using a BSGS ``args`` can be a BSGS @@ -382,7 +478,6 @@ def tensorsymmetry(*args): ``[[2, 2]]`` slot symmetry of the Riemann tensor ``[[1],[1]]`` vector*vector - TODO implement this with arbitrary Young tableaux Examples ======== @@ -431,7 +526,20 @@ def tableau2bsgs(a): class TensorType(Basic): """ - A TensorType object is characterised by its index types and its symmetry + Class of tensor types. + + Parameters + ========== + + ``index_types`` : list of ``TensorIndexType`` of the tensor indices + ``symmetry`` : ``TensorSymmetry`` of the tensor + + Attributes + ========== + + ``index_types`` + ``symmetry`` + ``types`` : list of ``TensorIndexType`` without repetitions Examples ======== @@ -534,45 +642,80 @@ def tensorhead(name, typ, sym, comm=0): class TensorHead(Basic): - is_commutative = False + """ + Tensor head of the tensor - def __new__(cls, name, typ, comm, **kw_args): - """ - tensor with given name, index types, symmetry, commutation rule + Parameters + ========== - ``name`` name of the tensor + ``name`` : name of the tensor - ``typ`` list of TensorIndexType + ``typ`` : list of TensorIndexType - ``comm`` commutation group number - see ``_TensorManager.set_comm`` + ``comm`` : commutation group number + Attributes + ========== - Examples - ======== + ``name`` + ``index_types`` + ``rank`` + ``types`` : equal to ``typ.types`` + ``symmetry`` : equal to ``typ.symmetry`` + ``comm`` : commutation group - >>> from sympy.tensor.tensor import TensorIndexType, tensorsymmetry, TensorType - >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') - >>> sym2 = tensorsymmetry([1]*2) - >>> S2 = TensorType([Lorentz]*2, sym2) - >>> A = S2('A') - """ + Notes + ===== + + A ``TensorHead`` belongs to a commutation group, defined by a + symbol on number ``comm`` (see ``_TensorManager.set_comm``); + tensors in a commutation group have the same commutation properties; + by default ``comm`` is ``0``, the group of the commuting tensors. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensorsymmetry, TensorType + >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + >>> sym2 = tensorsymmetry([1]*2) + >>> S2 = TensorType([Lorentz]*2, sym2) + >>> A = S2('A') + """ + is_commutative = False + + def __new__(cls, name, typ, comm, **kw_args): assert isinstance(name, basestring) obj = Basic.__new__(cls, name, typ, **kw_args) - obj.rank = len(obj.index_types) - obj.types = typ.types - obj.symmetry = typ.symmetry - obj.comm = TensorManager.comm_symbols2i(comm) + obj._rank = len(obj.index_types) + obj._types = typ.types + obj._symmetry = typ.symmetry + obj._comm = TensorManager.comm_symbols2i(comm) return obj @property def name(self): return self.args[0] + @property + def rank(self): + return self._rank + + @property + def types(self): + return self._types[:] + + @property + def symmetry(self): + return self._symmetry + + @property + def comm(self): + return self._comm + @property def index_types(self): - return self.args[1].index_types + return self.args[1].index_types[:] def __lt__(self, other): return (self.name, self.index_types) < (other.name, other.index_types) @@ -591,9 +734,8 @@ def commutes_with(self, other): return r - def __str__(self): + def _pretty(self): return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) - __repr__ = __str__ def __call__(self, *indices): """ @@ -658,10 +800,10 @@ def __new__(cls, t, **kw_args): typ = TensorType(types, symmetry) obj = Basic.__new__(cls, name, typ, t) obj.free_args = t.free_args - obj.comm = comm - obj.symmetry = symmetry - obj.types = types - obj.rank = rank + obj._comm = comm + obj._symmetry = symmetry + obj._types = types + obj._rank = rank return obj name = property(lambda self: self.args[0]) @@ -675,15 +817,20 @@ def call(self, *indices): class TensExpr(Basic): """ + Abstract base class for tensor expressions + + Notes + ===== + A tensor expression is an expression formed by tensors; currently the sums of tensors are distributed. - A TensExpr can be a TensAdd or a TensMul. + A ``TensExpr`` can be a ``TensAdd`` or a ``TensMul``. - TensAdd objects are put in canonic form using the Butler-Portugal + ``TensAdd`` objects are put in canonical form using the Butler-Portugal algorithm for canonicalization under monoterm symmetries. - TensMul objects are formed by products of component tensors, + ``TensMul`` objects are formed by products of component tensors, and include a coefficient, which is a SymPy expression. @@ -825,6 +972,21 @@ class TensAdd(TensExpr): """ Sum of tensors + Parameters + ========== + + ``free_args`` : list of the free indices + + Attributes + ========== + + ``args`` : tuple of addends + ``rank`` : rank of the tensor + ``free_args`` : list of the free indices in sorted order + + Notes + ===== + Sum of more than one tensor are put automatically in canonical form. Examples @@ -841,9 +1003,6 @@ class TensAdd(TensExpr): """ def __new__(cls, *args, **kw_args): - """ - args tuple of addends - """ args = [sympify(x) for x in args if x] args = _tensAdd_flatten(args) if not args: @@ -874,6 +1033,9 @@ def __new__(cls, *args, **kw_args): def free_args(self): return self.args[0].free_args + @property + def rank(self): + return self.args[0]._rank def __call__(self, *indices): """Returns tensor with ordered free indices replaced by ``indices`` @@ -966,7 +1128,10 @@ def contract_metric(self, g, contract_all=False): """ Raise or lower indices with the metric ``g`` - ``g`` metric + Parameters + ========== + + ``g`` : metric ``contract_all`` if True, eliminate all ``g`` which are contracted """ @@ -989,7 +1154,10 @@ def fun_eval(self, *index_tuples): """ Return a tensor with free indices substituted according to ``index_tuples`` - ``index_types`` list of tuples ``(old_index, new_index)`` + Parameters + ========== + + ``index_types`` : list of tuples ``(old_index, new_index)`` Examples ======== @@ -1025,7 +1193,10 @@ def substitute_indices(self, *index_tuples): """ Return a tensor with free indices substituted according to ``index_tuples`` - ``index_types`` list of tuples ``(old_index, new_index)`` + Parameters + ========== + + ``index_types`` : list of tuples ``(old_index, new_index)`` Examples ======== @@ -1055,33 +1226,51 @@ def _pretty(self): class TensMul(TensExpr): """ Product of tensors - """ - def __new__(cls, coeff, *args, **kw_args): - """ + Parameters + ========== - coeff SymPy expression coefficient of the tensor. + ``coeff`` : SymPy coefficient of the tensor + ``args`` - ``args[0]`` list of TensorHead of the component tensors. + Attributes + ========== - ``args[1]`` list of ``(ind, ipos, icomp)`` - where ``ind`` is a free index, ``ipos`` is the slot position - of ``ind`` in the ``icomp``-th component tensor. + ``_components`` : list of ``TensorHead`` of the component tensors + ``types`` : list of nonrepeated ``TensorIndexType`` + ``free`` : list of ``(ind, ipos, icomp)``, see Notes + ``dum`` : list of ``(ipos1, ipos2, icomp1, icomp2)``, see Notes + ``ext_rank`` : rank of the tensor counting the dummy indices + ``rank`` : rank of the tensor + ``coeff`` : SymPy coefficient of the tensor + ``free_args``: list of the free indices in sorted order + ``is_canon_bp`` : ``True`` if the tensor in in canonical form - ``args[2]`` list of tuples representing dummy indices. - (ipos1, ipos2, icomp1, icomp2) indicates that the contravariant - dummy index is the ``ipos1``-th slot position in the ``icomp1``-th - component tensor; the corresponding covariant index is - in the ``ipos2`` slot position in the ``icomp2``-th component tensor. - """ + Notes + ===== + + ``args[0]`` list of ``TensorHead`` of the component tensors. + + ``args[1]`` list of ``(ind, ipos, icomp)`` + where ``ind`` is a free index, ``ipos`` is the slot position + of ``ind`` in the ``icomp``-th component tensor. + + ``args[2]`` list of tuples representing dummy indices. + ``(ipos1, ipos2, icomp1, icomp2)`` indicates that the contravariant + dummy index is the ``ipos1``-th slot position in the ``icomp1``-th + component tensor; the corresponding covariant index is + in the ``ipos2`` slot position in the ``icomp2``-th component tensor. + """ + + def __new__(cls, coeff, *args, **kw_args): obj = Basic.__new__(cls) obj._components = args[0] - obj.types = [] + obj._types = [] for t in obj._components: - obj.types.extend(t.types) + obj.types.extend(t._types) obj._free = args[1] obj._dum = args[2] - obj.rank = len(obj._free) + 2*len(obj._dum) + obj._ext_rank = len(obj._free) + 2*len(obj._dum) obj._coeff = coeff obj._is_canon_bp = kw_args.get('is_canon_bp', False) @@ -1091,6 +1280,30 @@ def __new__(cls, coeff, *args, **kw_args): def free_args(self): return sorted([x[0] for x in self._free]) + @property + def components(self): + return self._components[:] + + @property + def free(self): + return self._free[:] + + @property + def coeff(self): + return self._coeff + + @property + def dum(self): + return self._dum[:] + + @property + def rank(self): + return len(self._free) + + @property + def types(self): + return self._types[:] + def __eq__(self, other): if other == 0: return self._coeff == 0 @@ -1203,6 +1416,8 @@ def get_indices(self): The indices are listed in the order in which they appear in the component tensors. + The dummy indices are given a name which does not collide with + the names of the free indices. Examples ======== @@ -1216,7 +1431,7 @@ def get_indices(self): >>> t.get_indices() [m1, m0, m2] """ - indices = [None]*self.rank + indices = [None]*self._ext_rank start = 0 pos = 0 vpos = [] @@ -1289,7 +1504,7 @@ def canon_args(self): from sympy.combinatorics.permutations import _af_new types = list(set(self.types)) types.sort(key = lambda x: x.name) - n = self.rank + n = self._ext_rank g = [None]*n + [n, n+1] pos = 0 vpos = [] @@ -1460,7 +1675,7 @@ def perm2tensor(self, g, canon_bp=False): sorted_free = [x[0] for x in self._free] sorted_free.sort() nfree = len(sorted_free) - rank = self.rank + rank = self._ext_rank indices = [None]*rank dum = [[None]*4 for i in range((rank - nfree)//2)] free = [] From 6a78685e998fc88694899bf13b195f64ef55a0f4 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Fri, 11 Jan 2013 22:48:07 +0100 Subject: [PATCH 41/47] use private attributes in code --- sympy/tensor/tensor.py | 136 ++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index cc1bac373614..9b655aada0d6 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -236,10 +236,11 @@ class TensorIndexType(Basic): def __new__(cls, name, metric=False, dim=None, eps_dim = None, dummy_fmt=None): obj = Basic.__new__(cls, name, metric) + obj._name = name if not dummy_fmt: - obj.dummy_fmt = '%s_%%d' % obj.name + obj._dummy_fmt = '%s_%%d' % obj.name else: - obj.dummy_fmt = '%s_%%d' % dummy_fmt + obj._dummy_fmt = '%s_%%d' % dummy_fmt if metric is None: obj.metric_antisym = None obj.metric = None @@ -262,7 +263,7 @@ def __new__(cls, name, metric=False, dim=None, eps_dim = None, @property def name(self): - return self.args[0] + return self._name @property def dim(self): @@ -280,6 +281,10 @@ def eps_dim(self): def epsilon(self): return self._epsilon + @property + def dummy_fmt(self): + return self._dummy_fmt + def get_kronecker_delta(self): sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) S2 = TensorType([self]*2, sym2) @@ -287,10 +292,10 @@ def get_kronecker_delta(self): return delta def get_epsilon(self): - if not isinstance(self.eps_dim, int): + if not isinstance(self._eps_dim, int): return None - sym = TensorSymmetry(get_symmetric_group_sgs(self.eps_dim, 1)) - Sdim = TensorType([self]*self.eps_dim, sym) + sym = TensorSymmetry(get_symmetric_group_sgs(self._eps_dim, 1)) + Sdim = TensorType([self]*self._eps_dim, sym) epsilon = Sdim('Eps') return epsilon @@ -347,32 +352,35 @@ class TensorIndex(Basic): def __new__(cls, name, tensortype, is_up=True): obj = Basic.__new__(cls, name, tensortype, is_up) + obj._name = name + obj._tensortype = tensortype + obj._is_up = is_up return obj @property def name(self): - return self.args[0] + return self._name @property def tensortype(self): - return self.args[1] + return self._tensortype @property def is_up(self): - return self.args[2] + return self._is_up def _pretty(self): - s = self.name - if not self.is_up: + s = self._name + if not self._is_up: s = '-%s' % s return s def __lt__(self, other): - return (self.tensortype, self.name) < (other.tensortype, other.name) + return (self._tensortype, self._name) < (other._tensortype, other._name) def __neg__(self): - t1 = TensorIndex(self.name, self.tensortype, - (not self.is_up)) + t1 = TensorIndex(self._name, self._tensortype, + (not self._is_up)) return t1 def tensor_indices(s, typ): @@ -687,6 +695,7 @@ def __new__(cls, name, typ, comm, **kw_args): assert isinstance(name, basestring) obj = Basic.__new__(cls, name, typ, **kw_args) + obj._name = obj.args[0] obj._rank = len(obj.index_types) obj._types = typ.types obj._symmetry = typ.symmetry @@ -695,7 +704,7 @@ def __new__(cls, name, typ, comm, **kw_args): @property def name(self): - return self.args[0] + return self._name @property def rank(self): @@ -709,6 +718,10 @@ def types(self): def symmetry(self): return self._symmetry + @property + def typ(self): + return self.args[1] + @property def comm(self): return self._comm @@ -721,7 +734,7 @@ def __lt__(self, other): return (self.name, self.index_types) < (other.name, other.index_types) def _hashable_content(self): - r = (self.name, tuple(self.types), self.symmetry, self.comm) + r = (self._name, tuple(self._types), self._symmetry, self._comm) return r def commutes_with(self, other): @@ -730,7 +743,7 @@ def commutes_with(self, other): Returns None if self and other do not (anti)commute. """ - r = TensorManager.get_comm(self.comm, other.comm) + r = TensorManager.get_comm(self._comm, other._comm) return r @@ -750,7 +763,7 @@ def __call__(self, *indices): >>> A = tensorhead('A', [Lorentz]*2, [[1]*2]) >>> t = A(a, -b) """ - if not [indices[i].tensortype for i in range(len(indices))] == self.index_types: + if not [indices[i]._tensortype for i in range(len(indices))] == self.index_types: raise ValueError('wrong index type') components = [self] free, dum = TensMul.from_indices(*indices) @@ -799,6 +812,7 @@ def __new__(cls, t, **kw_args): name = str(t) typ = TensorType(types, symmetry) obj = Basic.__new__(cls, name, typ, t) + obj._name = name obj.free_args = t.free_args obj._comm = comm obj._symmetry = symmetry @@ -806,7 +820,7 @@ def __new__(cls, t, **kw_args): obj._rank = rank return obj - name = property(lambda self: self.args[0]) + name = property(lambda self: self._name) t = property(lambda self: self.args[2]) @@ -1040,6 +1054,11 @@ def rank(self): def __call__(self, *indices): """Returns tensor with ordered free indices replaced by ``indices`` + Parameters + ========== + + ``indices`` + Examples ======== @@ -1058,7 +1077,7 @@ def __call__(self, *indices): """ free_args = self.free_args indices = list(indices) - if [x.tensortype for x in indices] != [x.tensortype for x in free_args]: + if [x._tensortype for x in indices] != [x._tensortype for x in free_args]: raise ValueError('incompatible types') if indices == free_args: return self @@ -1082,7 +1101,7 @@ def __eq__(self, other): if not isinstance(other, TensExpr): if len(self.args) == 1: return self.args[0]._coeff == other - if isinstance(other, TensExpr) and isinstance(other, TensMul ) \ + if isinstance(other, TensExpr) and isinstance(other, TensMul) \ and other._coeff == 0: return self == 0 t = self - other @@ -1133,7 +1152,7 @@ def contract_metric(self, g, contract_all=False): ``g`` : metric - ``contract_all`` if True, eliminate all ``g`` which are contracted + ``contract_all`` : if True, eliminate all ``g`` which are contracted """ args = [x.contract_metric(g, contract_all) for x in self.args] @@ -1267,7 +1286,7 @@ def __new__(cls, coeff, *args, **kw_args): obj._components = args[0] obj._types = [] for t in obj._components: - obj.types.extend(t._types) + obj._types.extend(t._types) obj._free = args[1] obj._dum = args[2] obj._ext_rank = len(obj._free) + 2*len(obj._dum) @@ -1380,9 +1399,9 @@ def from_indices(*indices): index_dict = {} dum = [] for i, index in enumerate(indices): - name = index.name - typ = index.tensortype - contr = index.is_up + name = index._name + typ = index._tensortype + contr = index._is_up if (name, typ) in index_dict: # found a pair of dummy indices is_contr, pos = index_dict[(name, typ)] @@ -1404,7 +1423,7 @@ def from_indices(*indices): else: dum.append((pos, i, 0, 0)) else: - index_dict[(name, typ)] = index.is_up, i + index_dict[(name, typ)] = index._is_up, i free = [(index, i, 0) for i, index in enumerate(indices) if free[i]] free.sort() @@ -1438,14 +1457,14 @@ def get_indices(self): components = self._components for t in components: vpos.append(pos) - pos += t.rank + pos += t._rank cdt = defaultdict(int) # if the free indices have names with dummy_fmt, start with an # index higher than those for the dummy indices # to avoid name collisions for indx, ipos, cpos in self._free: - if indx.name.split('_')[0] == indx.tensortype.dummy_fmt[:-3]: - cdt[indx.tensortype] = max(cdt[indx.tensortype], int(indx.name.split('_')[1]) + 1) + if indx._name.split('_')[0] == indx._tensortype._dummy_fmt[:-3]: + cdt[indx._tensortype] = max(cdt[indx._tensortype], int(indx._name.split('_')[1]) + 1) start = vpos[cpos] indices[start + ipos] = indx for ipos1, ipos2, cpos1, cpos2 in self._dum: @@ -1488,8 +1507,8 @@ def split(self): return [TensMul(self._coeff, [], [], [])] res = [] for t in components: - t1 = t(*indices[pos:pos + t.rank]) - pos += t.rank + t1 = t(*indices[pos:pos + t._rank]) + pos += t._rank res.append(t1) res[0] = TensMul(self._coeff, res[0]._components, res[0]._free, res[0]._dum, is_canon_bp=res[0]._is_canon_bp) return res @@ -1502,8 +1521,8 @@ def canon_args(self): """ # to be called after sorted_components from sympy.combinatorics.permutations import _af_new - types = list(set(self.types)) - types.sort(key = lambda x: x.name) + types = list(set(self._types)) + types.sort(key = lambda x: x._name) n = self._ext_rank g = [None]*n + [n, n+1] pos = 0 @@ -1511,7 +1530,7 @@ def canon_args(self): components = self._components for t in components: vpos.append(pos) - pos += t.rank + pos += t._rank # ordered indices: first the free indices, ordered by types # then the dummy indices, ordered by types and contravariant before # covariant @@ -1553,11 +1572,11 @@ def canon_args(self): numtyp.append([prev, 1]) v = [] for h, n in numtyp: - if h.comm == 0 or h.comm == 1: - comm = h.comm + if h._comm == 0 or h._comm == 1: + comm = h._comm else: - comm = TensorManager.get_comm(h.comm, h.comm) - v.append((h.symmetry.base, h.symmetry.generators, n, comm)) + comm = TensorManager.get_comm(h._comm, h._comm) + v.append((h._symmetry.base, h._symmetry.generators, n, comm)) return _af_new(g), dummies, msym, v def __mul__(self, other): @@ -1604,9 +1623,9 @@ def __mul__(self, other): ipos1, cpos1, ind1 = free_dict1[name] ipos2, cpos2, ind2 = free_dict2[name] cpos2 += nc1 - if ind1.is_up == ind2.is_up: + if ind1._is_up == ind2._is_up: raise ValueError('wrong index contruction %s' % ind1) - if ind1.is_up: + if ind1._is_up: new_dummy = (ipos1, ipos2, cpos1, cpos2) else: new_dummy = (ipos2, ipos1, cpos2, cpos1) @@ -1628,6 +1647,9 @@ def __div__(self, other): def sorted_components(self): """ Returns a tensor with sorted components + + The sorting is done taking into account the commutation group + of the component tensors. """ from sympy.combinatorics.permutations import _af_invert cv = zip(self._components, range(len(self._components))) @@ -1638,8 +1660,8 @@ def sorted_components(self): c = cv[j-1][0].commutes_with(cv[j][0]) if c not in [0, 1]: continue - if (cv[j-1][0].types, cv[j-1][0].name) > \ - (cv[j][0].types, cv[j][0].name): + if (cv[j-1][0]._types, cv[j-1][0]._name) > \ + (cv[j][0]._types, cv[j][0]._name): cv[j-1], cv[j] = cv[j], cv[j-1] if c: sign = -sign @@ -1671,7 +1693,7 @@ def perm2tensor(self, g, canon_bp=False): pos = 0 for t in components: vpos.append(pos) - pos += t.rank + pos += t._rank sorted_free = [x[0] for x in self._free] sorted_free.sort() nfree = len(sorted_free) @@ -1788,7 +1810,7 @@ def _contract(self, g, antisym, contract_all=False): return res def contract_delta(self, delta): - typ = delta.types[0] + typ = delta._types[0] t = self._contract(delta, None, True) return t @@ -1840,8 +1862,8 @@ def substitute_indices(self, *index_tuples): free1 = [] for j, ipos, cpos in free: for i, v in index_tuples: - if i.name == j.name and i.tensortype == j.tensortype: - if i.is_up == j.is_up: + if i._name == j._name and i._tensortype == j._tensortype: + if i._is_up == j._is_up: free1.append((v, ipos, cpos)) else: free1.append((-v, ipos, cpos)) @@ -1946,7 +1968,7 @@ def __call__(self, *indices): """ free_args = self.free_args indices = list(indices) - if [x.tensortype for x in indices] != [x.tensortype for x in free_args]: + if [x._tensortype for x in indices] != [x._tensortype for x in free_args]: raise ValueError('incompatible types') if indices == free_args: return self @@ -2094,9 +2116,9 @@ def _contract_g_with_itself(a, i, tg, tg_free, g, antisym): typ = g.index_types[0] a1 = a[:i] + a[i + 1:] t11 = tensor_mul(*a1) - if typ.dim is None: + if typ._dim is None: raise ValueError('dimension not assigned') - coeff = typ.dim*a[i]._coeff + coeff = typ._dim*a[i]._coeff if antisym and tg._dum[0][0] == 0: # g(i, -i) = -D coeff = -coeff @@ -2128,8 +2150,8 @@ def _contract_g_with_free_index(a, free_indices, i, tg, tg_free, g, antisym): free1.append((indx, iposx, 0)) coeff = tx._coeff if antisym: - if ind.is_up and ind == tg_free[0][0] or \ - (not ind.is_up) and ind == tg_free[1][0]: + if ind._is_up and ind == tg_free[0][0] or \ + (not ind._is_up) and ind == tg_free[1][0]: # g(i1, i0)*psi(-i1) = -psi(i0) # g(-i0, -i1)*psi(i1) = -psi(-i0) coeff = -coeff @@ -2160,10 +2182,10 @@ def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, anti # the two `g` are completely contracted # i < k always a = a[:i] + a[i+1:k] + a[k+1:] - coeff = coeff*typ.dim*tg._coeff*ty._coeff + coeff = coeff*typ._dim*tg._coeff*ty._coeff if antisym: ty_free = sorted(ty_free, key=lambda x: x[1]) - if ind1.is_up == ind2.is_up: + if ind1._is_up == ind2._is_up: # g(i,j)*g(-i,-j) = g(-i,-j)*g(i,j) = dim # g(i,j)*g(-j,-i) = g(-i,-j)*g(j,i) = -dim if ind1m == ty_free[1][0]: @@ -2193,7 +2215,7 @@ def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, anti if indx == ind2m: iposx2 = iposx if antisym: - if ind1.is_up == ind2.is_up: + if ind1._is_up == ind2._is_up: if iposx1 < iposx2: coeff = -coeff dum2.append((iposx1, iposx2, 0, 0)) @@ -2221,11 +2243,11 @@ def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, anti for indx, iposx, _ in ty._free: if indx == ind2m: free2.append((ind1, iposx, 0)) - if indx.is_up: + if indx._is_up: coeff = -coeff else: free2.append((indx, iposx, 0)) - if not indx.is_up: + if not indx._is_up: coeff = -coeff dum2 = ty._dum From 8cc10a7dd2b6ce1480aa79d98bee7851e1f69a26 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 12 Jan 2013 11:56:10 +0100 Subject: [PATCH 42/47] eliminated explicit call to subclasses in TensExpr; fixed bugs in _TensorManager --- sympy/tensor/tensor.py | 47 +++++++++++++++++++++++++------ sympy/tensor/tests/test_tensor.py | 20 ++++++++++--- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 9b655aada0d6..51b344dac4fc 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -50,6 +50,9 @@ class _TensorManager(object): Other groups can be defined using `set_comm`. """ def __init__(self): + self._comm_init() + + def _comm_init(self): self._comm = defaultdict(dict) self._comm[0][0] = 0 self._comm[0][1] = 0 @@ -61,6 +64,10 @@ def __init__(self): self._comm_symbols2i = {0:0, 1:1, 2:2} self._comm_i2symbol = {0:0, 1:1, 2:2} + @property + def comm(self): + return self._comm + def comm_symbols2i(self, i): """ get the commutation group number corresponding to ``i`` @@ -72,6 +79,7 @@ def comm_symbols2i(self, i): self._comm[n][0] = 0 self._comm[0][n] = 0 self._comm_symbols2i[i] = n + self._comm_i2symbol[n] = i return n return self._comm_symbols2i[i] @@ -125,7 +133,7 @@ def set_comm(self, i, j, c): self._comm[0][n] = 0 self._comm_symbols2i[i] = n self._comm_i2symbol[n] = i - if j not in self._comm.keys(): + if j not in self._comm_symbols2i: n = len(self._comm_symbols2i) self._comm[0][n] = 0 self._comm[n][0] = 0 @@ -154,8 +162,8 @@ def get_comm(self, i, j): return None def clear(self): - for i in range(3, len(self._comm)): - self._comm[i].clear() + self._comm_init() + TensorManager = _TensorManager() @@ -860,28 +868,28 @@ class TensExpr(Basic): is_commutative = False def __neg__(self): - return (-1)*self + return self*S.NegativeOne def __abs__(self): raise NotImplementedError def __add__(self, other): - return TensAdd(self, other) + raise NotImplementedError def __radd__(self, other): - return TensAdd(other, self) + raise NotImplementedError def __sub__(self, other): - return TensAdd(self, -other) + raise NotImplementedError def __rsub__(self, other): - return TensAdd(other, -self) + raise NotImplementedError def __mul__(self, other): raise NotImplementedError def __rmul__(self, other): - return TensAdd(*[x*other for x in self.args]) + return self*other def __pow__(self, other): raise NotImplementedError @@ -1118,6 +1126,15 @@ def __add__(self, other): args = self.args + (other,) return TensAdd(*args) + def __radd__(self, other): + return TensAdd(other, self) + + def __sub__(self, other): + return TensAdd(self, -other) + + def __rsub__(self, other): + return TensAdd(other, -self) + def __mul__(self, other): return TensAdd(*[x*other for x in self.args]) @@ -1579,6 +1596,18 @@ def canon_args(self): v.append((h._symmetry.base, h._symmetry.generators, n, comm)) return _af_new(g), dummies, msym, v + def __add__(self, other): + return TensAdd(self, other) + + def __radd__(self, other): + return TensAdd(other, self) + + def __sub__(self, other): + return TensAdd(self, -other) + + def __rsub__(self, other): + return TensAdd(other, -self) + def __mul__(self, other): """ Multiply two tensors using Einstein summation convention. diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 8b39a6077dc0..a40d87bb7916 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -856,9 +856,10 @@ def test_TensorManager(): LorentzH = TensorIndexType('LorentzH', dummy_fmt='LH') i, j = tensor_indices('i,j', Lorentz) ih, jh = tensor_indices('ih,jh', LorentzH) - TensorManager.set_comm(3, 4, 0) p, q = tensorhead('p q', [Lorentz], [[1]]) ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) + + TensorManager.set_comm(3, 4, 0) G = tensorhead('G', [Lorentz], [[1]], 3) GH = tensorhead('GH', [LorentzH], [[1]], 4) ps = G(i)*p(-i) @@ -871,13 +872,10 @@ def test_TensorManager(): assert ps*qsh == qsh*ps assert ps*qs != qs*ps - TensorManager.clear() # same as before, but using symbols in TensorManager Gsymbol = Symbol('Gsymbol') GHsymbol = Symbol('GHsymbol') TensorManager.set_comm(Gsymbol, GHsymbol, 0) - p, q = tensorhead('p q', [Lorentz], [[1]]) - ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) G = tensorhead('G', [Lorentz], [[1]], Gsymbol) assert TensorManager._comm_i2symbol[G.comm] == Gsymbol GH = tensorhead('GH', [LorentzH], [[1]], GHsymbol) @@ -891,6 +889,20 @@ def test_TensorManager(): assert ps*qsh == qsh*ps assert ps*qs != qs*ps + # do it again the other way + TensorManager.set_comm(3, 4, 0) + G = tensorhead('G', [Lorentz], [[1]], 3) + GH = tensorhead('GH', [LorentzH], [[1]], 4) + ps = G(i)*p(-i) + psh = GH(ih)*ph(-ih) + t = ps + psh + t1 = t*t + assert t1 == ps*ps + 2*ps*psh + psh*psh + qs = G(i)*q(-i) + qsh = GH(ih)*qh(-ih) + assert ps*qsh == qsh*ps + assert ps*qs != qs*ps + def test_hash(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') From 015abf57366b49462776a074569f41011e94a3e9 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Sat, 12 Jan 2013 19:59:44 +0100 Subject: [PATCH 43/47] Updated to PR 1700 --- sympy/tensor/tensor.py | 70 ++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 51b344dac4fc..7160dc2ccfe5 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -42,12 +42,14 @@ class _TensorManager(object): ===== Tensors belong to tensor commutation groups; each group has a label - ``comm`; there are predefined labels: - `0` tensors commuting with any other tensor - `1` tensors anticommuting among themselves - `2` tensors not commuting, apart with those with ``comm=0`` - - Other groups can be defined using `set_comm`. + ``comm``; there are predefined labels: + ``0`` tensors commuting with any other tensor + ``1`` tensors anticommuting among themselves + ``2`` tensors not commuting, apart with those with ``comm=0`` + + Other groups can be defined using ``set_comm``; tensors in those + groups commute with those with ``comm=0``; by default they + do not commute with any other group. """ def __init__(self): self._comm_init() @@ -72,7 +74,10 @@ def comm_symbols2i(self, i): """ get the commutation group number corresponding to ``i`` - ``i`` can be a symbol or a number + ``i`` can be a symbol or a number or a string + + If ``i`` is not already defined its commutation group number + is set. """ if i not in self._comm_symbols2i: n = len(self._comm_symbols2i) @@ -84,29 +89,38 @@ def comm_symbols2i(self, i): return self._comm_symbols2i[i] def comm_i2symbol(self, i): + """ + Returns the symbol corresponding to the commutation group number. + """ return self._comm_i2symbol[i] def set_comm(self, i, j, c): """ set the commutation parameter ``c`` for commutation groups ``i, j`` - ``i, j`` they can be symbols or numbers, apart from ``0, 1`` and ``2`` - which are reserved respectively for commuting, anticommuting - tensors and tensors not commuting with any other group apart with - the commuting tensors. + Parameters + ========== - ``c`` commutation number - 0 commuting - 1 anticommuting - None no commutation property + ``i, j`` : symbols representing commutation groups - Tensors in the group ``i=0`` commute with any other tensor. - Tensors in the group ``i=1`` anticommute within that group. - Tensors in the group ``i=2`` commute only with the ``0`` group. + ``c`` : group commutation number + Notes + ===== + + ``i, j`` can be symbols, strings or numbers, + apart from ``0, 1`` and ``2`` which are reserved respectively + for commuting, anticommuting tensors and tensors not commuting + with any other group apart with the commuting tensors. For the remaining cases, use this method to set the commutation rules; by default ``c=None``. + The group commutation number ``c`` is assigned in corrispondence + to the group commutation symbols; it can be + 0 commuting + 1 anticommuting + None no commutation property + Examples ======== @@ -127,6 +141,9 @@ def set_comm(self, i, j, c): >>> (G(i1)*A(i0)).canon_bp() A(i0)*G(i1) """ + if c not in (0, 1, None): + raise ValueError('`c` can assume only the values 0, 1 or None') + if i not in self._comm_symbols2i: n = len(self._comm_symbols2i) self._comm[n][0] = 0 @@ -145,6 +162,14 @@ def set_comm(self, i, j, c): self._comm[nj][ni] = c def set_comms(self, *args): + """ + set the commutation group numbers ``c`` for symbols ``i, j`` + + Parameters + ========== + + ``args`` : sequence of ``(i, j, c`` + """ for i, j, c in range(len(args)): set_comm(self, i, j, c) @@ -162,6 +187,9 @@ def get_comm(self, i, j): return None def clear(self): + """ + Clear the TensorManager. + """ self._comm_init() @@ -1144,6 +1172,8 @@ def __div__(self, other): raise ValueError('cannot divide by a tensor') return TensAdd(*[x/other for x in self.args]) + __truediv__ = __div__ + def _hashable_content(self): return tuple(self.args) @@ -1673,6 +1703,8 @@ def __div__(self, other): coeff = self._coeff/other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) + __truediv__ = __div__ + def sorted_components(self): """ Returns a tensor with sorted components @@ -2032,7 +2064,7 @@ def _pretty(self): a.append('%s(%s)' % (t.name, ', '.join(sindices[pos:pos + t.rank]))) else: a.append('%s' % t.name) - pos += t.rank + pos += t._rank res = '*'. join(a) if self._coeff == S.One: return res From e9be929e626324cac0e87b5f1a122ff18b5e6d5a Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Mon, 14 Jan 2013 00:05:16 +0100 Subject: [PATCH 44/47] Code clean up, fixed some bugs and improved coverage. --- sympy/tensor/tensor.py | 86 +++++++++-------- sympy/tensor/tests/test_tensor.py | 153 +++++++++++++++++++++++++++++- 2 files changed, 194 insertions(+), 45 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 7160dc2ccfe5..2fb0a0a9b376 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -55,12 +55,11 @@ def __init__(self): self._comm_init() def _comm_init(self): - self._comm = defaultdict(dict) - self._comm[0][0] = 0 - self._comm[0][1] = 0 - self._comm[1][0] = 1 - self._comm[2][0] = 1 - self._comm[0][2] = 1 + self._comm = [{} for i in range(3)] + for i in range(3): + self._comm[0][i] = 0 + self._comm[i][0] = 0 + self._comm[1][1] = 1 self._comm[2][1] = None self._comm[1][2] = None self._comm_symbols2i = {0:0, 1:1, 2:2} @@ -80,7 +79,8 @@ def comm_symbols2i(self, i): is set. """ if i not in self._comm_symbols2i: - n = len(self._comm_symbols2i) + n = len(self._comm) + self._comm.append({}) self._comm[n][0] = 0 self._comm[0][n] = 0 self._comm_symbols2i[i] = n @@ -145,13 +145,15 @@ def set_comm(self, i, j, c): raise ValueError('`c` can assume only the values 0, 1 or None') if i not in self._comm_symbols2i: - n = len(self._comm_symbols2i) + n = len(self._comm) + self._comm.append({}) self._comm[n][0] = 0 self._comm[0][n] = 0 self._comm_symbols2i[i] = n self._comm_i2symbol[n] = i if j not in self._comm_symbols2i: - n = len(self._comm_symbols2i) + n = len(self._comm) + self._comm.append({}) self._comm[0][n] = 0 self._comm[n][0] = 0 self._comm_symbols2i[j] = n @@ -170,21 +172,16 @@ def set_comms(self, *args): ``args`` : sequence of ``(i, j, c`` """ - for i, j, c in range(len(args)): - set_comm(self, i, j, c) + for i, j, c in args: + self.set_comm(i, j, c) def get_comm(self, i, j): """ - Return the commutation parameter for commutation groups ``i, j`` + Return the commutation parameter for commutation groups numbers ``i, j`` see ``_TensorManager.set_comm`` """ - try: - return self._comm[i].get(j, 0 if i*j == 0 else None) - except IndexError: - if j == 0: - return 0 - return None + return self._comm[i].get(j, 0 if i == 0 or j == 0 else None) def clear(self): """ @@ -944,7 +941,6 @@ def _tensAdd_collect_terms(args): prev = args[0] prev_coeff = prev._coeff changed = False - new = 0 for x in args[1:]: # if x and prev have the same tensor, update the coeff of prev @@ -995,8 +991,6 @@ def _tensAdd_flatten(args): t0 = args1[0] if isinstance(t0, TensAdd): t0 = t0.args[0] - if t0._free: - raise ValueError('all tensors must have the same indices') t1 = TensMul(Add(*args2), [], [], []) args = [t1] + args1 a = [] @@ -1085,7 +1079,7 @@ def free_args(self): @property def rank(self): - return self.args[0]._rank + return self.args[0].rank def __call__(self, *indices): """Returns tensor with ordered free indices replaced by ``indices`` @@ -1137,9 +1131,6 @@ def __eq__(self, other): if not isinstance(other, TensExpr): if len(self.args) == 1: return self.args[0]._coeff == other - if isinstance(other, TensExpr) and isinstance(other, TensMul) \ - and other._coeff == 0: - return self == 0 t = self - other if not isinstance(t, TensExpr): return t == 0 @@ -1150,9 +1141,8 @@ def __eq__(self, other): return all(x._coeff == 0 for x in t.args) def __add__(self, other): - other = sympify(other) args = self.args + (other,) - return TensAdd(*args) + return TensAdd(self, other) def __radd__(self, other): return TensAdd(other, self) @@ -1172,7 +1162,11 @@ def __div__(self, other): raise ValueError('cannot divide by a tensor') return TensAdd(*[x/other for x in self.args]) + def __rdiv__(self, other): + raise ValueError('cannot divide by a tensor') + __truediv__ = __div__ + __truerdiv__ = __rdiv__ def _hashable_content(self): return tuple(self.args) @@ -1185,8 +1179,6 @@ def __ne__(self, other): def contract_delta(self, delta): args = [x.contract_delta(delta) for x in self.args] - if len(args) == 1: - return args[0] t = TensAdd(*args) return canon_bp(t) @@ -1200,11 +1192,14 @@ def contract_metric(self, g, contract_all=False): ``g`` : metric ``contract_all`` : if True, eliminate all ``g`` which are contracted + + Notes + ===== + + see the ``TensorIndexType`` docstring for the contraction conventions """ args = [x.contract_metric(g, contract_all) for x in self.args] - if len(args) == 1: - return args[0] t = TensAdd(*args) return canon_bp(t) @@ -1693,6 +1688,7 @@ def __mul__(self, other): return TensMul(coeff, components, free, dum) def __rmul__(self, other): + other = sympify(other) coeff = other*self._coeff return TensMul(coeff, self._components, self._free, self._dum) @@ -1703,7 +1699,11 @@ def __div__(self, other): coeff = self._coeff/other return TensMul(coeff, self._components, self._free, self._dum, is_canon_bp=self._is_canon_bp) + def __rdiv__(self, other): + raise ValueError('cannot divide by a tensor') + __truediv__ = __div__ + __truerdiv__ = __rdiv__ def sorted_components(self): """ @@ -1883,6 +1883,11 @@ def contract_metric(self, g, contract_all=False): ``contract_all`` if True, eliminate all ``g`` which are contracted + Notes + ===== + + see the ``TensorIndexType`` docstring for the contraction conventions + Examples ======== @@ -1897,8 +1902,6 @@ def contract_metric(self, g, contract_all=False): >>> t.contract_metric(g).canon_bp() p(L_0)*q(-L_0) """ - if g.index_types[0].metric_antisym is None: - raise NotImplementedError return self._contract(g, g.index_types[0].metric_antisym, contract_all) def substitute_indices(self, *index_tuples): @@ -2136,8 +2139,11 @@ def riemann_cyclic(t2): >>> riemann_cyclic(t) 0 """ - a = t2.args - a1 = [x.split() for x in a] + if isinstance(t2, TensMul): + args = [t2] + else: + args = t2.args + a1 = [x.split() for x in args] a2 = [[riemann_cyclic_replace(tx) for tx in y] for y in a1] a3 = [tensor_mul(*v) for v in a2] t3 = TensAdd(*a3) @@ -2238,7 +2244,7 @@ def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, anti # ty has the index ind2m ty_free = ty._free[:] if ty._components == [g]: - ty_indices = [x[0] for x in ty._free] + ty_indices = [x[0] for x in ty_free] if all(x in [ind1m, ind2m] for x in ty_indices): # the two `g` are completely contracted # i < k always @@ -2270,7 +2276,7 @@ def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, anti # tg has both indices contracted with ty free2 = [(indx, iposx, cposx) for indx, iposx, cposx in ty._free if indx != ind1m and indx != ind2m] dum2 = ty._dum[:] - for indx, iposx, _ in ty._free: + for indx, iposx, _ in ty_free: if indx == ind1m: iposx1 = iposx if indx == ind2m: @@ -2295,21 +2301,19 @@ def _contract_g_without_free_index(a, free_indices, i, tg, tg_free, g, typ, anti free2 = [] if not antisym: - for indx, iposx, _ in ty._free: + for indx, iposx, _ in ty_free: if indx == ind2m: free2.append((ind1, iposx, 0)) else: free2.append((indx, iposx, 0)) else: - for indx, iposx, _ in ty._free: + for indx, iposx, _ in ty_free: if indx == ind2m: free2.append((ind1, iposx, 0)) if indx._is_up: coeff = -coeff else: free2.append((indx, iposx, 0)) - if not indx._is_up: - coeff = -coeff dum2 = ty._dum t2 = TensMul(ty._coeff, ty._components, free2, dum2) diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index a40d87bb7916..826e0f07a79d 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -5,7 +5,7 @@ TensorSymmetry, get_symmetric_group_sgs, TensorType, TensorIndex, tensor_mul, canon_bp, TensAdd, riemann_cyclic_replace, riemann_cyclic, tensorlist_contract_metric, TensMul, tensorsymmetry, tensorhead, - TensorManager, HoldTensorHead) + TensorManager, HoldTensorHead, TensExpr) from sympy.utilities.pytest import raises #################### Tests from tensor_can.py ####################### @@ -405,6 +405,21 @@ def test_canonicalize2(): t1 = t.canon_bp() assert t1 == 0 +def test_canonicalize3(): + D = Symbol('D') + Spinor = TensorIndexType('Spinor', dim=D, metric=True, dummy_fmt='S') + a0,a1,a2,a3,a4 = tensor_indices('a0:5', Spinor) + C = Spinor.metric + chi, psi = tensorhead('chi,psi', [Spinor], [[1]], 1) + + t = chi(a1)*psi(a0) + t1 = t.canon_bp() + assert t1 == t + + t = psi(a1)*chi(a0) + t1 = t.canon_bp() + assert t1 == -chi(a0)*psi(a1) + class Metric(Basic): def __new__(cls, name, antisym, **kwargs): obj = Basic.__new__(cls, name, antisym, **kwargs) @@ -418,15 +433,26 @@ def test_TensorIndexType(): Lorentz = TensorIndexType('Lorentz', metric=G, dim=D, dummy_fmt='L') m0, m1, m2, m3, m4 = tensor_indices('m0:5', Lorentz) sym2 = tensorsymmetry([1]*2) + sym2n = tensorsymmetry(*get_symmetric_group_sgs(2)) + assert sym2 == sym2n g = Lorentz.metric p = tensorhead('p', [Lorentz], [[1]]) assert str(g) == 'g(Lorentz,Lorentz)' t = g(m0, m1)*p(-m1) t1 = t.contract_metric(g) assert t1 == p(m0) + assert Lorentz.eps_dim == Lorentz.dim + + TSpace = TensorIndexType('TSpace') + i0, i1 = tensor_indices('i0 i1', TSpace) + g = TSpace.metric + A = tensorhead('A', [TSpace]*2, [[1]*2]) + assert str(A(i0,-i0).canon_bp()) == 'A(TSpace_0, -TSpace_0)' + def test_get_indices(): + from sympy.abc import x Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) assert a != -a @@ -436,19 +462,66 @@ def test_get_indices(): indices = t.get_indices() L_0 = TensorIndex('L_0', Lorentz) assert indices == [a, L_0, -L_0, c] - a = t.split() - t2 = tensor_mul(*a) + t2 = tensor_mul(*t.split()) assert t == t2 assert tensor_mul(*[]) == TensMul(S.One, [],[],[]) + sym = tensorsymmetry([1]*2) + assert A.typ == TensorType([Lorentz]*2, sym) + assert A.symmetry == sym + sym1 = TensorSymmetry(get_symmetric_group_sgs(2)) + assert sym == sym1 + assert A.types == [Lorentz] + typ = TensorType([Lorentz]*2, sym) + assert str(typ) == "TensorType(['Lorentz', 'Lorentz'])" + + t = TensMul(S.One, [],[],[]) + assert str(t) == '1' + t = (1 + x)*A(a, b) + assert str(t) == '(x + 1)*A(a, b)' + assert t.types == [Lorentz] + assert t.rank == 2 + assert t.dum == [] + assert t.coeff == 1 + x + assert sorted(t.free) == [(a, 0, 0), (b, 1, 0)] + assert t.components == [A] + t = A(a, b) + B(a, b) + assert t.rank == 2 + t1 = t - A(a, b) - B(a, b) + assert t1 == 0 + t = 1 - (A(a, -a) + B(a, -a)) + t1 = 1 + (A(a, -a) + B(a, -a)) + assert t + t1 == 2 + t2 = 1 + A(a, -a) + assert t1 != t2 + raises(ValueError, lambda: typ(2)) + raises(ValueError, lambda: A(a,b,c)) + raises(ValueError, lambda: tensor_indices(3, typ)) + g = Lorentz.metric + raises(ValueError, lambda: g(a, -a).contract_metric(g)) + raises(ValueError, lambda: g(c, d)/g(a, b)) + raises(ValueError, lambda: S.One/g(a, b)) + raises(ValueError, lambda: (A(c, d) + g(c, d))/g(a, b)) + raises(ValueError, lambda: S.One/(A(c, d) + g(c, d))) + raises(ValueError, lambda: A(a, b) + A(a, c)) + raises(NotImplementedError, lambda: TensExpr.__mul__(t2, 'a')) + raises(NotImplementedError, lambda: TensExpr.__add__(t2, 'a')) + raises(NotImplementedError, lambda: TensExpr.__radd__(t2, 'a')) + raises(NotImplementedError, lambda: TensExpr.__sub__(t2, 'a')) + raises(NotImplementedError, lambda: TensExpr.__div__(t2, 'a')) + raises(NotImplementedError, lambda: A(a, b)**2) + raises(NotImplementedError, lambda: 2**A(a, b)) + raises(NotImplementedError, lambda: abs(A(a, b))) def test_add1(): + assert TensAdd() == 0 # simple example of algebraic expression Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a,b,d0,d1,i,j,k = tensor_indices('a,b,d0,d1,i,j,k', Lorentz) # A, B symmetric A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) t1 = A(b,-d0)*B(d0,a) + assert TensAdd(t1) == t1 t2a = B(d0,a) + A(d0, a) t2 = A(b,-d0)*t2a assert str(t2) == 'A(a, L_0)*A(b, -L_0) + A(b, L_0)*B(a, -L_0)' @@ -487,6 +560,14 @@ def test_add1(): t = (p(i) + q(i))/2 assert 2*t == p(i) + q(i) + t = S.One - p(i)*p(-i) + assert t + p(-j)*p(j) == 1 + t = S.One + p(i)*p(-i) + assert t - p(-j)*p(j) == 1 + + t = TensMul(1, [], [], []) + assert t.split()[0] == t + def test_add2(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') m, n, p, q = tensor_indices('m,n,p,q', Lorentz) @@ -557,6 +638,9 @@ def test_riemann_cyclic(): t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) t1 = riemann_cyclic(t) assert t1 == 0 + t = R(i,j,k,l) + t1 = riemann_cyclic(t) + assert t1 == -S(1)/3*R(i, l, j, k) + S(1)/3*R(i, k, j, l) + S(2)/3*R(i, j, k, l) t = R(i,j,k,l)*R(-k,-l,m,n)*(R(-m,-n,-i,-j) + 2*R(-m,-j,-n,-i)) t1 = riemann_cyclic(t) @@ -709,10 +793,38 @@ def test_metric_contract3(): chi, psi = tensorhead('chi,psi', [Spinor], [[1]], 1) B = tensorhead('B', [Spinor]*2, [[1],[1]]) + t = C(a0, -a0) + t1 = t.contract_metric(C) + assert t1 == -D + + t = C(-a0, a0) + t1 = t.contract_metric(C) + assert t1 == D + + t = C(a0,a1)*C(-a0,-a1) + t1 = t.contract_metric(C) + assert t1 == D + + t = C(a1,a0)*C(-a0,-a1) + t1 = t.contract_metric(C) + assert t1 == -D + + t = C(-a0,a1)*C(a0,-a1) + t1 = t.contract_metric(C) + assert t1 == -D + + t = C(a1,-a0)*C(a0,-a1) + t1 = t.contract_metric(C) + assert t1 == D + t = C(a0,a1)*B(-a1,-a0) t1 = t.contract_metric(C) assert t1 == B(a0, -a0) + t = C(a1,a0)*B(-a1,-a0) + t1 = t.contract_metric(C) + assert t1 == -B(a0, -a0) + t = C(a0,-a1)*B(a1,-a0) t1 = t.contract_metric(C) assert t1 == -B(a0, -a0) @@ -725,6 +837,18 @@ def test_metric_contract3(): t1 = t.contract_metric(C) assert t1 == B(a0, -a0) + t = C(-a1, a0)*B(a1,-a0) + t1 = t.contract_metric(C) + assert t1 == B(a0, -a0) + + t = C(a0,a1)*psi(-a1) + t1 = t.contract_metric(C) + assert t1 == psi(a0) + + t = C(a1,a0)*psi(-a1) + t1 = t.contract_metric(C) + assert t1 == -psi(a0) + t = C(a0,a1)*chi(-a0)*psi(-a1) t1 = t.contract_metric(C) assert t1 == -chi(a1)*psi(-a1) @@ -749,6 +873,14 @@ def test_metric_contract3(): t1 = t.contract_metric(C) assert t1 == -chi(-a1)*psi(a1) + t = C(-a1,-a0)*B(a0,a2)*psi(a1) + t1 = t.contract_metric(C) + assert t1 == -B(-a1,a2)*psi(a1) + + t = C(a1,a0)*B(-a2,-a0)*psi(-a1) + t1 = t.contract_metric(C) + assert t1 == B(-a2,a1)*psi(-a1) + def test_epsilon(): Lorentz = TensorIndexType('Lorentz', dim=4, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) @@ -859,8 +991,8 @@ def test_TensorManager(): p, q = tensorhead('p q', [Lorentz], [[1]]) ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) - TensorManager.set_comm(3, 4, 0) G = tensorhead('G', [Lorentz], [[1]], 3) + TensorManager.set_comm(3, 4, 0) GH = tensorhead('GH', [LorentzH], [[1]], 4) ps = G(i)*p(-i) psh = GH(ih)*ph(-ih) @@ -888,11 +1020,16 @@ def test_TensorManager(): qsh = GH(ih)*qh(-ih) assert ps*qsh == qsh*ps assert ps*qs != qs*ps + n = TensorManager.comm_symbols2i(Gsymbol) + assert TensorManager.comm_i2symbol(n) == Gsymbol # do it again the other way TensorManager.set_comm(3, 4, 0) G = tensorhead('G', [Lorentz], [[1]], 3) + n3 = TensorManager.comm_symbols2i(3) GH = tensorhead('GH', [LorentzH], [[1]], 4) + n4 = TensorManager.comm_symbols2i(4) + assert TensorManager.get_comm(n3, n4) == 0 ps = G(i)*p(-i) psh = GH(ih)*ph(-ih) t = ps + psh @@ -903,6 +1040,14 @@ def test_TensorManager(): assert ps*qsh == qsh*ps assert ps*qs != qs*ps + assert GHsymbol in TensorManager._comm_symbols2i + TensorManager.clear() + assert TensorManager.comm == [{0:0, 1:0, 2:0}, {0:0, 1:1, 2:None}, {0:0, 1:None}] + assert GHsymbol not in TensorManager._comm_symbols2i + TensorManager.set_comms((3,4,0),(3,1,1)) + assert TensorManager.get_comm(3, 1) == TensorManager.get_comm(1, 3) == 1 + raises(ValueError, lambda: TensorManager.set_comm(4, 5, 2)) + def test_hash(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') From 54f81afbd63d3525ac06223facd868777bf62807 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Mon, 14 Jan 2013 11:26:10 +0100 Subject: [PATCH 45/47] cleaned up the tests --- sympy/tensor/tests/test_tensor.py | 148 ++++++++++++++---------------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index 826e0f07a79d..cfcf4a90ee6c 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -436,11 +436,7 @@ def test_TensorIndexType(): sym2n = tensorsymmetry(*get_symmetric_group_sgs(2)) assert sym2 == sym2n g = Lorentz.metric - p = tensorhead('p', [Lorentz], [[1]]) assert str(g) == 'g(Lorentz,Lorentz)' - t = g(m0, m1)*p(-m1) - t1 = t.contract_metric(g) - assert t1 == p(m0) assert Lorentz.eps_dim == Lorentz.dim TSpace = TensorIndexType('TSpace') @@ -449,10 +445,7 @@ def test_TensorIndexType(): A = tensorhead('A', [TSpace]*2, [[1]*2]) assert str(A(i0,-i0).canon_bp()) == 'A(TSpace_0, -TSpace_0)' - - -def test_get_indices(): - from sympy.abc import x +def test_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) assert a != -a @@ -462,53 +455,44 @@ def test_get_indices(): indices = t.get_indices() L_0 = TensorIndex('L_0', Lorentz) assert indices == [a, L_0, -L_0, c] - t2 = tensor_mul(*t.split()) - assert t == t2 - assert tensor_mul(*[]) == TensMul(S.One, [],[],[]) + raises(ValueError, lambda: tensor_indices(3, Lorentz)) + raises(ValueError, lambda: A(a,b,c)) +def test_tensorsymmetry(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') sym = tensorsymmetry([1]*2) - assert A.typ == TensorType([Lorentz]*2, sym) - assert A.symmetry == sym sym1 = TensorSymmetry(get_symmetric_group_sgs(2)) assert sym == sym1 + sym = tensorsymmetry([2]) + sym1 = TensorSymmetry(get_symmetric_group_sgs(2, 1)) + assert sym == sym1 + +def test_TensorType(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + sym = tensorsymmetry([1]*2) + A = tensorhead('A', [Lorentz]*2, [[1]*2]) + assert A.typ == TensorType([Lorentz]*2, sym) assert A.types == [Lorentz] typ = TensorType([Lorentz]*2, sym) assert str(typ) == "TensorType(['Lorentz', 'Lorentz'])" - - t = TensMul(S.One, [],[],[]) - assert str(t) == '1' - t = (1 + x)*A(a, b) - assert str(t) == '(x + 1)*A(a, b)' - assert t.types == [Lorentz] - assert t.rank == 2 - assert t.dum == [] - assert t.coeff == 1 + x - assert sorted(t.free) == [(a, 0, 0), (b, 1, 0)] - assert t.components == [A] - t = A(a, b) + B(a, b) - assert t.rank == 2 - t1 = t - A(a, b) - B(a, b) - assert t1 == 0 - t = 1 - (A(a, -a) + B(a, -a)) - t1 = 1 + (A(a, -a) + B(a, -a)) - assert t + t1 == 2 - t2 = 1 + A(a, -a) - assert t1 != t2 raises(ValueError, lambda: typ(2)) - raises(ValueError, lambda: A(a,b,c)) - raises(ValueError, lambda: tensor_indices(3, typ)) + +def test_TensExpr(): + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b, c, d = tensor_indices('a,b,c,d', Lorentz) g = Lorentz.metric - raises(ValueError, lambda: g(a, -a).contract_metric(g)) + A, B = tensorhead('A B', [Lorentz]*2, [[1]*2]) raises(ValueError, lambda: g(c, d)/g(a, b)) raises(ValueError, lambda: S.One/g(a, b)) raises(ValueError, lambda: (A(c, d) + g(c, d))/g(a, b)) raises(ValueError, lambda: S.One/(A(c, d) + g(c, d))) raises(ValueError, lambda: A(a, b) + A(a, c)) - raises(NotImplementedError, lambda: TensExpr.__mul__(t2, 'a')) - raises(NotImplementedError, lambda: TensExpr.__add__(t2, 'a')) - raises(NotImplementedError, lambda: TensExpr.__radd__(t2, 'a')) - raises(NotImplementedError, lambda: TensExpr.__sub__(t2, 'a')) - raises(NotImplementedError, lambda: TensExpr.__div__(t2, 'a')) + t = A(a, b) + B(a, b) + raises(NotImplementedError, lambda: TensExpr.__mul__(t, 'a')) + raises(NotImplementedError, lambda: TensExpr.__add__(t, 'a')) + raises(NotImplementedError, lambda: TensExpr.__radd__(t, 'a')) + raises(NotImplementedError, lambda: TensExpr.__sub__(t, 'a')) + raises(NotImplementedError, lambda: TensExpr.__div__(t, 'a')) raises(NotImplementedError, lambda: A(a, b)**2) raises(NotImplementedError, lambda: 2**A(a, b)) raises(NotImplementedError, lambda: abs(A(a, b))) @@ -565,8 +549,15 @@ def test_add1(): t = S.One + p(i)*p(-i) assert t - p(-j)*p(j) == 1 - t = TensMul(1, [], [], []) - assert t.split()[0] == t + t = A(a, b) + B(a, b) + assert t.rank == 2 + t1 = t - A(a, b) - B(a, b) + assert t1 == 0 + t = 1 - (A(a, -a) + B(a, -a)) + t1 = 1 + (A(a, -a) + B(a, -a)) + assert t + t1 == 2 + t2 = 1 + A(a, -a) + assert t1 != t2 def test_add2(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -580,6 +571,31 @@ def test_add2(): t2 = t1*A(-n,-p,-q) assert t2 == 0 +def test_mul(): + from sympy.abc import x + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + sym = tensorsymmetry([1]*2) + t = TensMul(S.One, [],[],[]) + assert str(t) == '1' + A, B = tensorhead('A B', [Lorentz]*2, [[1]*2]) + t = (1 + x)*A(a, b) + assert str(t) == '(x + 1)*A(a, b)' + assert t.types == [Lorentz] + assert t.rank == 2 + assert t.dum == [] + assert t.coeff == 1 + x + assert sorted(t.free) == [(a, 0, 0), (b, 1, 0)] + assert t.components == [A] + + t = A(-b, a)*B(-a, c)*A(-c, d) + t1 = tensor_mul(*t.split()) + assert t == t1 + assert tensor_mul(*[]) == TensMul(S.One, [],[],[]) + + t = TensMul(1, [], [], []) + assert t.split()[0] == t + def test_substitute_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -665,6 +681,10 @@ def test_contract_metric1(): Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') a, b, c, d, e = tensor_indices('a,b,c,d,e', Lorentz) g = Lorentz.metric + p = tensorhead('p', [Lorentz], [[1]]) + t = g(a, b)*p(-b) + t1 = t.contract_metric(g) + assert t1 == p(a) A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) # case with g with all free indices @@ -697,6 +717,11 @@ def test_contract_metric1(): assert t2 == A(a, -a) assert not t2._free + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a, b = tensor_indices('a,b', Lorentz) + g = Lorentz.metric + raises(ValueError, lambda: g(a, -a).contract_metric(g)) # no dim + def test_contract_metric2(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') @@ -991,20 +1016,6 @@ def test_TensorManager(): p, q = tensorhead('p q', [Lorentz], [[1]]) ph, qh = tensorhead('ph qh', [LorentzH], [[1]]) - G = tensorhead('G', [Lorentz], [[1]], 3) - TensorManager.set_comm(3, 4, 0) - GH = tensorhead('GH', [LorentzH], [[1]], 4) - ps = G(i)*p(-i) - psh = GH(ih)*ph(-ih) - t = ps + psh - t1 = t*t - assert t1 == ps*ps + 2*ps*psh + psh*psh - qs = G(i)*q(-i) - qsh = GH(ih)*qh(-ih) - assert ps*qsh == qsh*ps - assert ps*qs != qs*ps - - # same as before, but using symbols in TensorManager Gsymbol = Symbol('Gsymbol') GHsymbol = Symbol('GHsymbol') TensorManager.set_comm(Gsymbol, GHsymbol, 0) @@ -1023,30 +1034,13 @@ def test_TensorManager(): n = TensorManager.comm_symbols2i(Gsymbol) assert TensorManager.comm_i2symbol(n) == Gsymbol - # do it again the other way - TensorManager.set_comm(3, 4, 0) - G = tensorhead('G', [Lorentz], [[1]], 3) - n3 = TensorManager.comm_symbols2i(3) - GH = tensorhead('GH', [LorentzH], [[1]], 4) - n4 = TensorManager.comm_symbols2i(4) - assert TensorManager.get_comm(n3, n4) == 0 - ps = G(i)*p(-i) - psh = GH(ih)*ph(-ih) - t = ps + psh - t1 = t*t - assert t1 == ps*ps + 2*ps*psh + psh*psh - qs = G(i)*q(-i) - qsh = GH(ih)*qh(-ih) - assert ps*qsh == qsh*ps - assert ps*qs != qs*ps - assert GHsymbol in TensorManager._comm_symbols2i + raises(ValueError, lambda: TensorManager.set_comm(GHsymbol, 1, 2)) + TensorManager.set_comms((Gsymbol,GHsymbol,0),(Gsymbol,1,1)) + assert TensorManager.get_comm(n, 1) == TensorManager.get_comm(1, n) == 1 TensorManager.clear() assert TensorManager.comm == [{0:0, 1:0, 2:0}, {0:0, 1:1, 2:None}, {0:0, 1:None}] assert GHsymbol not in TensorManager._comm_symbols2i - TensorManager.set_comms((3,4,0),(3,1,1)) - assert TensorManager.get_comm(3, 1) == TensorManager.get_comm(1, 3) == 1 - raises(ValueError, lambda: TensorManager.set_comm(4, 5, 2)) def test_hash(): D = Symbol('D') From a07f6673fcd1164ebed087befe9212297db2400f Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Mon, 14 Jan 2013 17:00:39 +0100 Subject: [PATCH 46/47] Updated to PR 1700 --- sympy/tensor/tensor.py | 32 +++++++++++------------- sympy/tensor/tests/test_tensor.py | 41 ++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index 2fb0a0a9b376..b8b72bb7026d 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -177,7 +177,7 @@ def set_comms(self, *args): def get_comm(self, i, j): """ - Return the commutation parameter for commutation groups numbers ``i, j`` + Return the commutation parameter for commutation group numbers ``i, j`` see ``_TensorManager.set_comm`` """ @@ -494,7 +494,7 @@ def generators(self): @property def rank(self): - return self.args[1][0].size + return self.args[1][0].size - 2 def _hashable_content(self): r = (tuple(self.base), tuple(self.generators)) @@ -555,7 +555,7 @@ def tableau2bsgs(a): return bsgs if not args: - return TensorSymmetry([[], [Permutation(2)]]) + return TensorSymmetry([[], [Permutation(1)]]) if len(args) == 2 and isinstance(args[1][0], Permutation): return TensorSymmetry(args) base, sgs = tableau2bsgs(args[0]) @@ -596,7 +596,7 @@ class TensorType(Basic): is_commutative = False def __new__(cls, index_types, symmetry, **kw_args): - assert symmetry.rank == len(index_types) + 2 + assert symmetry.rank == len(index_types) obj = Basic.__new__(cls, index_types, symmetry, **kw_args) return obj @@ -988,9 +988,6 @@ def _tensAdd_flatten(args): args1.append(x) args1 = [x for x in args1 if isinstance(x, TensExpr) and x._coeff] args2 = [x for x in args if not isinstance(x, TensExpr)] - t0 = args1[0] - if isinstance(t0, TensAdd): - t0 = t0.args[0] t1 = TensMul(Add(*args2), [], [], []) args = [t1] + args1 a = [] @@ -1128,9 +1125,8 @@ def canon_bp(self): def __eq__(self, other): other = sympify(other) - if not isinstance(other, TensExpr): - if len(self.args) == 1: - return self.args[0]._coeff == other + if isinstance(other, TensMul) and other._coeff == 0: + return self == 0 t = self - other if not isinstance(t, TensExpr): return t == 0 @@ -1141,7 +1137,6 @@ def __eq__(self, other): return all(x._coeff == 0 for x in t.args) def __add__(self, other): - args = self.args + (other,) return TensAdd(self, other) def __radd__(self, other): @@ -1227,10 +1222,9 @@ def fun_eval(self, *index_tuples): >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) - >>> t = A(i, k)*B(-k, -j); t - A(i, L_0)*B(-L_0, -j) - >>> t.substitute_indices((i,j), (j, k)) - A(j, L_0)*B(-L_0, -k) + >>> t = A(i, k)*B(-k, -j) + A(i, -j) + >>> t.fun_eval((i, k),(-j, l)) + A(k, L_0)*B(l, -L_0) + A(k, l) """ args = self.args args1 = [] @@ -1266,6 +1260,10 @@ def substitute_indices(self, *index_tuples): >>> Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) + >>> t = A(i, k)*B(-k, -j); t + A(i, L_0)*B(-L_0, -j) + >>> t.substitute_indices((i,j), (j, k)) + A(j, L_0)*B(-L_0, -k) """ args = self.args args1 = [] @@ -1997,8 +1995,8 @@ def fun_eval(self, *index_tuples): >>> A, B = tensorhead('A,B', [Lorentz]*2, [[1]*2]) >>> t = A(i, k)*B(-k, -j); t A(i, L_0)*B(-L_0, -j) - >>> t.substitute_indices((i,j), (j, k)) - A(j, L_0)*B(-L_0, -k) + >>> t.fun_eval((i, k),(-j, l)) + A(k, L_0)*B(-L_0, l) """ free = self._free free1 = [] diff --git a/sympy/tensor/tests/test_tensor.py b/sympy/tensor/tests/test_tensor.py index cfcf4a90ee6c..ab047803173b 100644 --- a/sympy/tensor/tests/test_tensor.py +++ b/sympy/tensor/tests/test_tensor.py @@ -448,9 +448,9 @@ def test_TensorIndexType(): def test_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + assert a.tensortype == Lorentz assert a != -a A, B = tensorhead('A B', [Lorentz]*2, [[1]*2]) - assert A != B t = A(a,b)*B(-b,c) indices = t.get_indices() L_0 = TensorIndex('L_0', Lorentz) @@ -466,6 +466,9 @@ def test_tensorsymmetry(): sym = tensorsymmetry([2]) sym1 = TensorSymmetry(get_symmetric_group_sgs(2, 1)) assert sym == sym1 + sym2 = tensorsymmetry() + assert sym2.base == [] and sym2.generators == [Permutation(1)] + raises(NotImplementedError, lambda: tensorsymmetry([2, 1])) def test_TensorType(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -492,11 +495,23 @@ def test_TensExpr(): raises(NotImplementedError, lambda: TensExpr.__add__(t, 'a')) raises(NotImplementedError, lambda: TensExpr.__radd__(t, 'a')) raises(NotImplementedError, lambda: TensExpr.__sub__(t, 'a')) + raises(NotImplementedError, lambda: TensExpr.__rsub__(t, 'a')) raises(NotImplementedError, lambda: TensExpr.__div__(t, 'a')) + raises(NotImplementedError, lambda: TensExpr.__rdiv__(t, 'a')) raises(NotImplementedError, lambda: A(a, b)**2) raises(NotImplementedError, lambda: 2**A(a, b)) raises(NotImplementedError, lambda: abs(A(a, b))) +def test_TensorHead(): + assert TensAdd() == 0 + # simple example of algebraic expression + Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') + a,b = tensor_indices('a,b', Lorentz) + # A, B symmetric + A = tensorhead('A', [Lorentz]*2, [[1]*2]) + assert A.rank == 2 + assert A.symmetry == tensorsymmetry([1]*2) + def test_add1(): assert TensAdd() == 0 # simple example of algebraic expression @@ -558,6 +573,9 @@ def test_add1(): assert t + t1 == 2 t2 = 1 + A(a, -a) assert t1 != t2 + assert t2 != TensMul(0, [],[],[]) + t = p(i) + q(i) + raises(ValueError, lambda: t(i, j)) def test_add2(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -570,6 +588,8 @@ def test_add2(): t1 = S(2)/3*R(m,n,p,q) - S(1)/3*R(m,q,n,p) + S(1)/3*R(m,p,n,q) t2 = t1*A(-n,-p,-q) assert t2 == 0 + t = A(m, -m, n) + A(n, p, -p) + assert t == 0 def test_mul(): from sympy.abc import x @@ -590,12 +610,22 @@ def test_mul(): t = A(-b, a)*B(-a, c)*A(-c, d) t1 = tensor_mul(*t.split()) + assert t == t(-b, d) assert t == t1 assert tensor_mul(*[]) == TensMul(S.One, [],[],[]) t = TensMul(1, [], [], []) + zsym = tensorsymmetry() + typ = TensorType([], zsym) + C = typ('C') + assert str(C()) == 'C' + assert str(t) == '1' assert t.split()[0] == t - + raises(ValueError, lambda: TensMul.from_indices(a, a)) + raises(ValueError, lambda: TensMul.from_indices(-a, -a)) + raises(ValueError, lambda: A(a, b)*A(a, c)) + t = A(a, b)*A(-a, c) + raises(ValueError, lambda: t(a, b, c)) def test_substitute_indices(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -995,6 +1025,8 @@ def test_fun(): assert t(a,b,c) == t assert t - t(b,a,c) == q(c)*p(a)*q(b) - q(c)*p(b)*q(a) assert t(b,c,d) == q(d)*p(b)*q(c) + g(b,c)*g(d,e)*q(-e) + t1 = t.fun_eval((a,b),(b,a)) + assert t1 == q(c)*p(b)*q(a) + g(a,b)*g(c,d)*q(-d) # check that g_{a b; c} = 0 # example taken from L. Brewin @@ -1007,6 +1039,8 @@ def test_fun(): t = dg(-c,-a,-b) - g(-a,-d)*gamma(d,-b,-c) - g(-b,-d)*gamma(d,-a,-c) t = t.contract_metric(g, True) assert t == 0 + t = q(c)*p(a)*q(b) + assert t(b,c,d) == q(d)*p(b)*q(c) def test_TensorManager(): Lorentz = TensorIndexType('Lorentz', dummy_fmt='L') @@ -1041,7 +1075,8 @@ def test_TensorManager(): TensorManager.clear() assert TensorManager.comm == [{0:0, 1:0, 2:0}, {0:0, 1:1, 2:None}, {0:0, 1:None}] assert GHsymbol not in TensorManager._comm_symbols2i - + nh = TensorManager.comm_symbols2i(GHsymbol) + assert GHsymbol in TensorManager._comm_symbols2i def test_hash(): D = Symbol('D') Lorentz = TensorIndexType('Lorentz', dim=D, dummy_fmt='L') From 97e8bb3f2f812b3a44b1641abb9ab12ec2fc6ad1 Mon Sep 17 00:00:00 2001 From: Mario Pernici Date: Tue, 15 Jan 2013 12:34:46 +0100 Subject: [PATCH 47/47] updated to PR 1700 --- sympy/tensor/tensor.py | 79 +++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py index b8b72bb7026d..0f54d3f97ddd 100644 --- a/sympy/tensor/tensor.py +++ b/sympy/tensor/tensor.py @@ -43,8 +43,11 @@ class _TensorManager(object): Tensors belong to tensor commutation groups; each group has a label ``comm``; there are predefined labels: + ``0`` tensors commuting with any other tensor + ``1`` tensors anticommuting among themselves + ``2`` tensors not commuting, apart with those with ``comm=0`` Other groups can be defined using ``set_comm``; tensors in those @@ -101,9 +104,9 @@ def set_comm(self, i, j, c): Parameters ========== - ``i, j`` : symbols representing commutation groups + i, j : symbols representing commutation groups - ``c`` : group commutation number + c : group commutation number Notes ===== @@ -117,8 +120,11 @@ def set_comm(self, i, j, c): The group commutation number ``c`` is assigned in corrispondence to the group commutation symbols; it can be + 0 commuting + 1 anticommuting + None no commutation property Examples @@ -131,9 +137,9 @@ def set_comm(self, i, j, c): >>> Lorentz = TensorIndexType('Lorentz') >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) >>> A = tensorhead('A', [Lorentz], [[1]]) - >>> G = tensorhead('G', [Lorentz], [[1]], 3) - >>> GH = tensorhead('GH', [Lorentz], [[1]], 4) - >>> TensorManager.set_comm(3, 4, 0) + >>> G = tensorhead('G', [Lorentz], [[1]], 'Gcomm') + >>> GH = tensorhead('GH', [Lorentz], [[1]], 'GHcomm') + >>> TensorManager.set_comm('Gcomm', 'GHcomm', 0) >>> (GH(i1)*G(i0)).canon_bp() G(i0)*GH(i1) >>> (G(i1)*G(i0)).canon_bp() @@ -170,7 +176,7 @@ def set_comms(self, *args): Parameters ========== - ``args`` : sequence of ``(i, j, c`` + args : sequence of ``(i, j, c)`` """ for i, j, c in args: self.set_comm(i, j, c) @@ -199,16 +205,16 @@ class TensorIndexType(Basic): Parameters ========== - ``name`` : name of the tensor type + name : name of the tensor type - ``metric`` : metric symmetry or metric object or ``None`` + metric : metric symmetry or metric object or ``None`` - ``dim`` : dimension, it can be a symbol or an integer or ``None`` + dim : dimension, it can be a symbol or an integer or ``None`` - ``eps_dim`` : dimension of the epsilon tensor + eps_dim : dimension of the epsilon tensor - ``dummy_fmt`` : name of the head of dummy indices + dummy_fmt : name of the head of dummy indices Attributes ========== @@ -347,9 +353,9 @@ class TensorIndex(Basic): Parameters ========== - ``name`` : name of the index - ``tensortype`` : ``TensorIndexType`` of the index - ``is_up`` : flag for contravariant index + name : name of the index + tensortype : ``TensorIndexType`` of the index + is_up : flag for contravariant index Attributes ========== @@ -423,9 +429,9 @@ def tensor_indices(s, typ): Parameters ========== - ``s`` : string of comma separated names of indices + s : string of comma separated names of indices - ``typ`` : list of ``TensorIndexType`` of the indices + typ : list of ``TensorIndexType`` of the indices Examples ======== @@ -449,7 +455,7 @@ class TensorSymmetry(Basic): Parameters ========== - ``bsgs`` : tuple ``(base, sgs)`` BSGS of the symmetry of the tensor + bsgs : tuple ``(base, sgs)`` BSGS of the symmetry of the tensor Attributes ========== @@ -572,8 +578,8 @@ class TensorType(Basic): Parameters ========== - ``index_types`` : list of ``TensorIndexType`` of the tensor indices - ``symmetry`` : ``TensorSymmetry`` of the tensor + index_types : list of ``TensorIndexType`` of the tensor indices + symmetry : ``TensorSymmetry`` of the tensor Attributes ========== @@ -656,13 +662,16 @@ def tensorhead(name, typ, sym, comm=0): """ Function generating tensorhead(s). - ``name`` name or sequence of names (as in ``symbol``) + Parameters + ========== + + name : name or sequence of names (as in ``symbol``) - ``typ`` index types + typ : index types - ``sym`` same as ``*args`` in ``tensorsymmetry`` + sym : same as ``*args`` in ``tensorsymmetry`` - ``comm``: commutation group number + comm : commutation group number see ``_TensorManager.set_comm`` @@ -689,11 +698,11 @@ class TensorHead(Basic): Parameters ========== - ``name`` : name of the tensor + name : name of the tensor - ``typ`` : list of TensorIndexType + typ : list of TensorIndexType - ``comm`` : commutation group number + comm : commutation group number Attributes ========== @@ -1016,7 +1025,7 @@ class TensAdd(TensExpr): Parameters ========== - ``free_args`` : list of the free indices + free_args : list of the free indices Attributes ========== @@ -1084,7 +1093,7 @@ def __call__(self, *indices): Parameters ========== - ``indices`` + indices Examples ======== @@ -1184,9 +1193,9 @@ def contract_metric(self, g, contract_all=False): Parameters ========== - ``g`` : metric + g : metric - ``contract_all`` : if True, eliminate all ``g`` which are contracted + contract_all : if True, eliminate all ``g`` which are contracted Notes ===== @@ -1213,7 +1222,7 @@ def fun_eval(self, *index_tuples): Parameters ========== - ``index_types`` : list of tuples ``(old_index, new_index)`` + index_types : list of tuples ``(old_index, new_index)`` Examples ======== @@ -1251,7 +1260,7 @@ def substitute_indices(self, *index_tuples): Parameters ========== - ``index_types`` : list of tuples ``(old_index, new_index)`` + index_types : list of tuples ``(old_index, new_index)`` Examples ======== @@ -1289,8 +1298,8 @@ class TensMul(TensExpr): Parameters ========== - ``coeff`` : SymPy coefficient of the tensor - ``args`` + coeff : SymPy coefficient of the tensor + args Attributes ========== @@ -1302,7 +1311,7 @@ class TensMul(TensExpr): ``ext_rank`` : rank of the tensor counting the dummy indices ``rank`` : rank of the tensor ``coeff`` : SymPy coefficient of the tensor - ``free_args``: list of the free indices in sorted order + ``free_args`` : list of the free indices in sorted order ``is_canon_bp`` : ``True`` if the tensor in in canonical form Notes