Index notation for tensors¶
AUTHORS:
- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version 
- Léo Brunswic (2019): add multiple symmetries and multiple contractions 
- class sage.tensor.modules.tensor_with_indices.TensorWithIndices(tensor, indices)[source]¶
- Bases: - SageObject- Index notation for tensors. - This is a technical class to allow one to write some tensor operations (contractions and symmetrizations) in index notation. - INPUT: - tensor– a tensor (or a tensor field)
- indices– string containing the indices, as single letters; the contravariant indices must be stated first and separated from the covariant indices by the character- _
 - EXAMPLES: - Index representation of tensors on a rank-3 free module: - sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: b = M.tensor((0,2), name='b') sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] sage: t = a*b ; t.set_name('t') ; t Type-(2,2) tensor t on the 3-dimensional vector space M over the Rational Field sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: T = TensorWithIndices(t, '^ij_kl') ; T t^ij_kl - >>> from sage.all import * >>> M = FiniteRankFreeModule(QQ, Integer(3), name='M') >>> e = M.basis('e') >>> a = M.tensor((Integer(2),Integer(0)), name='a') >>> a[:] = [[Integer(1),Integer(2),Integer(3)], [Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),Integer(9)]] >>> b = M.tensor((Integer(0),Integer(2)), name='b') >>> b[:] = [[-Integer(1),Integer(2),-Integer(3)], [-Integer(4),Integer(5),Integer(6)], [Integer(7),-Integer(8),Integer(9)]] >>> t = a*b ; t.set_name('t') ; t Type-(2,2) tensor t on the 3-dimensional vector space M over the Rational Field >>> from sage.tensor.modules.tensor_with_indices import TensorWithIndices >>> T = TensorWithIndices(t, '^ij_kl') ; T t^ij_kl - The - TensorWithIndicesobject is returned by the square bracket operator acting on the tensor and fed with the string specifying the indices:- sage: a['^ij'] a^ij sage: type(a['^ij']) <class 'sage.tensor.modules.tensor_with_indices.TensorWithIndices'> sage: b['_ef'] b_ef sage: t['^ij_kl'] t^ij_kl - >>> from sage.all import * >>> a['^ij'] a^ij >>> type(a['^ij']) <class 'sage.tensor.modules.tensor_with_indices.TensorWithIndices'> >>> b['_ef'] b_ef >>> t['^ij_kl'] t^ij_kl - The symbol ‘^’ may be omitted, since the distinction between covariant and contravariant indices is performed by the index position relative to the symbol ‘_’: - sage: t['ij_kl'] t^ij_kl - >>> from sage.all import * >>> t['ij_kl'] t^ij_kl - Also, LaTeX notation may be used: - sage: t['^{ij}_{kl}'] t^ij_kl - >>> from sage.all import * >>> t['^{ij}_{kl}'] t^ij_kl - If some operation is asked in the index notation, the resulting tensor is returned, not a - TensorWithIndicesobject; for instance, for a symmetrization:- sage: s = t['^(ij)_kl'] ; s # the symmetrization on i,j is indicated by parentheses Type-(2,2) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: s == t.symmetrize(0,1) True - >>> from sage.all import * >>> s = t['^(ij)_kl'] ; s # the symmetrization on i,j is indicated by parentheses Type-(2,2) tensor on the 3-dimensional vector space M over the Rational Field >>> s.symmetries() symmetry: (0, 1); no antisymmetry >>> s == t.symmetrize(Integer(0),Integer(1)) True - The letters denoting the indices can be chosen freely; since they carry no information, they can even be replaced by dots: - sage: t['^(..)_..'] == t.symmetrize(0,1) True - >>> from sage.all import * >>> t['^(..)_..'] == t.symmetrize(Integer(0),Integer(1)) True - Similarly, for an antisymmetrization: - sage: s = t['^ij_[kl]'] ; s # the symmetrization on k,l is indicated by square brackets Type-(2,2) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (2, 3) sage: s == t.antisymmetrize(2,3) True - >>> from sage.all import * >>> s = t['^ij_[kl]'] ; s # the symmetrization on k,l is indicated by square brackets Type-(2,2) tensor on the 3-dimensional vector space M over the Rational Field >>> s.symmetries() no symmetry; antisymmetry: (2, 3) >>> s == t.antisymmetrize(Integer(2),Integer(3)) True - One can also perform multiple symmetrization-antisymmetrizations: - sage: aa = a*a sage: aa['(..)(..)'] == aa.symmetrize(0,1).symmetrize(2,3) True sage: aa == aa['(..)(..)'] + aa['[..][..]'] + aa['(..)[..]'] + aa['[..](..)'] True - >>> from sage.all import * >>> aa = a*a >>> aa['(..)(..)'] == aa.symmetrize(Integer(0),Integer(1)).symmetrize(Integer(2),Integer(3)) True >>> aa == aa['(..)(..)'] + aa['[..][..]'] + aa['(..)[..]'] + aa['[..](..)'] True - Another example of an operation indicated by indices is a contraction: - sage: s = t['^ki_kj'] ; s # contraction on the repeated index k Type-(1,1) tensor on the 3-dimensional vector space M over the Rational Field sage: s == t.trace(0,2) True - >>> from sage.all import * >>> s = t['^ki_kj'] ; s # contraction on the repeated index k Type-(1,1) tensor on the 3-dimensional vector space M over the Rational Field >>> s == t.trace(Integer(0),Integer(2)) True - Indices not involved in the contraction may be replaced by dots: - sage: s == t['^k._k.'] True - >>> from sage.all import * >>> s == t['^k._k.'] True - The contraction of two tensors is indicated by repeated indices and the - *operator:- sage: s = a['^ik'] * b['_kj'] ; s Type-(1,1) tensor on the 3-dimensional vector space M over the Rational Field sage: s == a.contract(1, b, 0) True sage: s = t['^.k_..'] * b['_.k'] ; s Type-(1,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s == t.contract(1, b, 1) True sage: t['^{ik}_{jl}']*b['_{mk}'] == s # LaTeX notation True - >>> from sage.all import * >>> s = a['^ik'] * b['_kj'] ; s Type-(1,1) tensor on the 3-dimensional vector space M over the Rational Field >>> s == a.contract(Integer(1), b, Integer(0)) True >>> s = t['^.k_..'] * b['_.k'] ; s Type-(1,3) tensor on the 3-dimensional vector space M over the Rational Field >>> s == t.contract(Integer(1), b, Integer(1)) True >>> t['^{ik}_{jl}']*b['_{mk}'] == s # LaTeX notation True - Contraction on two indices: - sage: s = a['^kl'] * b['_kl'] ; s 105 sage: s == (a*b)['^kl_kl'] True sage: s == (a*b)['_kl^kl'] True sage: s == a.contract(0,1, b, 0,1) True - >>> from sage.all import * >>> s = a['^kl'] * b['_kl'] ; s 105 >>> s == (a*b)['^kl_kl'] True >>> s == (a*b)['_kl^kl'] True >>> s == a.contract(Integer(0),Integer(1), b, Integer(0),Integer(1)) True - The square bracket operator acts in a similar way on - TensorWithIndices:- sage: b = +a["ij"] ; b._tensor.set_name("b") # create a copy of a["ij"] sage: b b^ij sage: b[:] [1 2 3] [4 5 6] [7 8 9] sage: b[0,0] == 1 True sage: b["ji"] b^ji sage: b["(ij)"][:] [1 3 5] [3 5 7] [5 7 9] sage: b["(ij)"] == b["(ij)"]["ij"] True - >>> from sage.all import * >>> b = +a["ij"] ; b._tensor.set_name("b") # create a copy of a["ij"] >>> b b^ij >>> b[:] [1 2 3] [4 5 6] [7 8 9] >>> b[Integer(0),Integer(0)] == Integer(1) True >>> b["ji"] b^ji >>> b["(ij)"][:] [1 3 5] [3 5 7] [5 7 9] >>> b["(ij)"] == b["(ij)"]["ij"] True - However, it keeps track of indices: - sage: b["ij"] = a["ji"] sage: b[:] == a[:] False sage: b[:] == a[:].transpose() True - >>> from sage.all import * >>> b["ij"] = a["ji"] >>> b[:] == a[:] False >>> b[:] == a[:].transpose() True - Arithmetics: - sage: 2*a['^ij'] X^ij sage: (2*a['^ij'])._tensor == 2*a True sage: 2*t['ij_kl'] X^ij_kl sage: +a['^ij'] +a^ij sage: +t['ij_kl'] +t^ij_kl sage: -a['^ij'] -a^ij sage: -t['ij_kl'] -t^ij_kl sage: a["^(..)"]["ij"] == 1/2*(a["^ij"] + a["^ji"]) True - >>> from sage.all import * >>> Integer(2)*a['^ij'] X^ij >>> (Integer(2)*a['^ij'])._tensor == Integer(2)*a True >>> Integer(2)*t['ij_kl'] X^ij_kl >>> +a['^ij'] +a^ij >>> +t['ij_kl'] +t^ij_kl >>> -a['^ij'] -a^ij >>> -t['ij_kl'] -t^ij_kl >>> a["^(..)"]["ij"] == Integer(1)/Integer(2)*(a["^ij"] + a["^ji"]) True - The output indices are the ones of the left term of the addition: - sage: a["^(..)"]["ji"] == 1/2*(a["^ij"] + a["^ji"]) False sage: (a*a)["^..(ij)"]["abij"] == 1/2*((a*a)["^abij"] + (a*a)["^abji"]) True sage: c = 1/2*((a*a)["^abij"] + (a*a)["^ijab"]) sage: from itertools import product sage: all(c[i,j,k,l] == c[k,l,i,j] for i,j,k,l in product(range(3),repeat=4)) True - >>> from sage.all import * >>> a["^(..)"]["ji"] == Integer(1)/Integer(2)*(a["^ij"] + a["^ji"]) False >>> (a*a)["^..(ij)"]["abij"] == Integer(1)/Integer(2)*((a*a)["^abij"] + (a*a)["^abji"]) True >>> c = Integer(1)/Integer(2)*((a*a)["^abij"] + (a*a)["^ijab"]) >>> from itertools import product >>> all(c[i,j,k,l] == c[k,l,i,j] for i,j,k,l in product(range(Integer(3)),repeat=Integer(4))) True - Non-digit unicode identifier characters are allowed: - sage: a['^μξ'] a^μξ - >>> from sage.all import * >>> a['^μξ'] a^μξ - Conventions are checked and non acceptable indices raise - ValueError, for instance:- sage: a['([..])'] # nested symmetries Traceback (most recent call last): ... ValueError: index conventions not satisfied sage: a['(..'] # unbalanced parenthis Traceback (most recent call last): ... ValueError: index conventions not satisfied sage: a['ii'] # repeated indices of the same type Traceback (most recent call last): ... ValueError: index conventions not satisfied: repeated indices of same type sage: (a*a)['^(ij)^(kl)'] # multiple indices group of the same type Traceback (most recent call last): ... ValueError: index conventions not satisfied sage: a["^\u2663\u2665"] # non-word-constituent Traceback (most recent call last): ... ValueError: index conventions not satisfied - >>> from sage.all import * >>> a['([..])'] # nested symmetries Traceback (most recent call last): ... ValueError: index conventions not satisfied >>> a['(..'] # unbalanced parenthis Traceback (most recent call last): ... ValueError: index conventions not satisfied >>> a['ii'] # repeated indices of the same type Traceback (most recent call last): ... ValueError: index conventions not satisfied: repeated indices of same type >>> (a*a)['^(ij)^(kl)'] # multiple indices group of the same type Traceback (most recent call last): ... ValueError: index conventions not satisfied >>> a["^\u2663\u2665"] # non-word-constituent Traceback (most recent call last): ... ValueError: index conventions not satisfied - permute_indices(permutation)[source]¶
- Return a tensor with indices with permuted indices. - INPUT: - permutation– permutation that has to be applied to the indices the input should be a- listcontaining the second line of the permutation in Cauchy notation.
 - OUTPUT: - an instance of - TensorWithIndiceswhose indices names and place are those of- selfbut whose components have been permuted with- permutation.
 - EXAMPLES: - sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: b = M.tensor((2,0), name='b') sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] sage: identity = [0,1] sage: transposition = [1,0] sage: a["ij"].permute_indices(identity) == a["ij"] True sage: a["ij"].permute_indices(transposition)[:] == a[:].transpose() True sage: cycle = [1,2,3,0] # the cyclic permutation sending 0 to 1 sage: (a*b)[0,1,2,0] == (a*b)["ijkl"].permute_indices(cycle)[1,2,0,0] True - >>> from sage.all import * >>> M = FiniteRankFreeModule(QQ, Integer(3), name='M') >>> e = M.basis('e') >>> a = M.tensor((Integer(2),Integer(0)), name='a') >>> a[:] = [[Integer(1),Integer(2),Integer(3)], [Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),Integer(9)]] >>> b = M.tensor((Integer(2),Integer(0)), name='b') >>> b[:] = [[-Integer(1),Integer(2),-Integer(3)], [-Integer(4),Integer(5),Integer(6)], [Integer(7),-Integer(8),Integer(9)]] >>> identity = [Integer(0),Integer(1)] >>> transposition = [Integer(1),Integer(0)] >>> a["ij"].permute_indices(identity) == a["ij"] True >>> a["ij"].permute_indices(transposition)[:] == a[:].transpose() True >>> cycle = [Integer(1),Integer(2),Integer(3),Integer(0)] # the cyclic permutation sending 0 to 1 >>> (a*b)[Integer(0),Integer(1),Integer(2),Integer(0)] == (a*b)["ijkl"].permute_indices(cycle)[Integer(1),Integer(2),Integer(0),Integer(0)] True 
 - update()[source]¶
- Return the tensor contains in - selfif it differs from that used for creating- self, otherwise return- self.- EXAMPLES: - sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: a = M.tensor((1,1), name='a') sage: a[:] = [[1,-2,3], [-4,5,-6], [7,-8,9]] sage: a_ind = TensorWithIndices(a, 'i_j') ; a_ind a^i_j sage: a_ind.update() a^i_j sage: a_ind.update() is a_ind True sage: a_ind = TensorWithIndices(a, 'k_k') ; a_ind scalar sage: a_ind.update() 15 - >>> from sage.all import * >>> from sage.tensor.modules.tensor_with_indices import TensorWithIndices >>> M = FiniteRankFreeModule(QQ, Integer(3), name='M') >>> e = M.basis('e') >>> a = M.tensor((Integer(1),Integer(1)), name='a') >>> a[:] = [[Integer(1),-Integer(2),Integer(3)], [-Integer(4),Integer(5),-Integer(6)], [Integer(7),-Integer(8),Integer(9)]] >>> a_ind = TensorWithIndices(a, 'i_j') ; a_ind a^i_j >>> a_ind.update() a^i_j >>> a_ind.update() is a_ind True >>> a_ind = TensorWithIndices(a, 'k_k') ; a_ind scalar >>> a_ind.update() 15