# Tensors on free modules#

The class FreeModuleTensor implements tensors on a free module $$M$$ of finite rank over a commutative ring. A tensor of type $$(k,l)$$ on $$M$$ is a multilinear map:

$\underbrace{M^*\times\cdots\times M^*}_{k\ \; \mbox{times}} \times \underbrace{M\times\cdots\times M}_{l\ \; \mbox{times}} \longrightarrow R$

where $$R$$ is the commutative ring over which the free module $$M$$ is defined and $$M^* = \mathrm{Hom}_R(M,R)$$ is the dual of $$M$$. The integer $$k + l$$ is called the tensor rank. The set $$T^{(k,l)}(M)$$ of tensors of type $$(k,l)$$ on $$M$$ is a free module of finite rank over $$R$$, described by the class TensorFreeModule.

Various derived classes of FreeModuleTensor are devoted to specific tensors:

Each of these classes is a Sage element class, the corresponding parent class being:

AUTHORS:

• Eric Gourgoulhon, Michal Bejger (2014-2015): initial version

• Michael Jung (2019): improve treatment of the zero element; add method copy_from

REFERENCES:

• Chap. 21 of R. Godement : Algebra [God1968]

• Chap. 12 of J. M. Lee: Introduction to Smooth Manifolds [Lee2013] (only when the free module is a vector space)

• Chap. 2 of B. O’Neill: Semi-Riemannian Geometry [ONe1983]

EXAMPLES:

A tensor of type $$(1, 1)$$ on a rank-3 free module over $$\ZZ$$:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: t = M.tensor((1,1), name='t') ; t
Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring
sage: t.parent()
Free module of type-(1,1) tensors on the Rank-3 free module M
over the Integer Ring
sage: t.parent() is M.tensor_module(1,1)
True
sage: t in M.tensor_module(1,1)
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> t = M.tensor((Integer(1),Integer(1)), name='t') ; t
Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring
>>> t.parent()
Free module of type-(1,1) tensors on the Rank-3 free module M
over the Integer Ring
>>> t.parent() is M.tensor_module(Integer(1),Integer(1))
True
>>> t in M.tensor_module(Integer(1),Integer(1))
True


Setting some component of the tensor in a given basis:

sage: e = M.basis('e') ; e
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
sage: t.set_comp(e)[0,0] = -3  # the component [0,0] w.r.t. basis e is set to -3

>>> from sage.all import *
>>> e = M.basis('e') ; e
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
>>> t.set_comp(e)[Integer(0),Integer(0)] = -Integer(3)  # the component [0,0] w.r.t. basis e is set to -3


The unset components are assumed to be zero:

sage: t.comp(e)[:]  # list of all components w.r.t. basis e
[-3  0  0]
[ 0  0  0]
[ 0  0  0]
sage: t.display(e)  # displays the expansion of t on the basis e_i⊗e^j of T^(1,1)(M)
t = -3 e_0⊗e^0

>>> from sage.all import *
>>> t.comp(e)[:]  # list of all components w.r.t. basis e
[-3  0  0]
[ 0  0  0]
[ 0  0  0]
>>> t.display(e)  # displays the expansion of t on the basis e_i⊗e^j of T^(1,1)(M)
t = -3 e_0⊗e^0


The commands t.set_comp(e) and t.comp(e) can be abridged by providing the basis as the first argument in the square brackets:

sage: t[e,0,0] = -3
sage: t[e,:]
[-3  0  0]
[ 0  0  0]
[ 0  0  0]

>>> from sage.all import *
>>> t[e,Integer(0),Integer(0)] = -Integer(3)
>>> t[e,:]
[-3  0  0]
[ 0  0  0]
[ 0  0  0]


Actually, since e is M’s default basis, the mention of e can be omitted:

sage: t[0,0] = -3
sage: t[:]
[-3  0  0]
[ 0  0  0]
[ 0  0  0]

>>> from sage.all import *
>>> t[Integer(0),Integer(0)] = -Integer(3)
>>> t[:]
[-3  0  0]
[ 0  0  0]
[ 0  0  0]


For tensors of rank 2, the matrix of components w.r.t. a given basis is obtained via the function matrix:

sage: matrix(t.comp(e))
[-3  0  0]
[ 0  0  0]
[ 0  0  0]
sage: matrix(t.comp(e)).parent()
Full MatrixSpace of 3 by 3 dense matrices over Integer Ring

>>> from sage.all import *
>>> matrix(t.comp(e))
[-3  0  0]
[ 0  0  0]
[ 0  0  0]
>>> matrix(t.comp(e)).parent()
Full MatrixSpace of 3 by 3 dense matrices over Integer Ring


Tensor components can be modified (reset) at any time:

sage: t[0,0] = 0
sage: t[:]
[0 0 0]
[0 0 0]
[0 0 0]

>>> from sage.all import *
>>> t[Integer(0),Integer(0)] = Integer(0)
>>> t[:]
[0 0 0]
[0 0 0]
[0 0 0]


Checking that t is zero:

sage: t.is_zero()
True
sage: t == 0
True
sage: t == M.tensor_module(1,1).zero()  # the zero element of the module of all type-(1,1) tensors on M
True

>>> from sage.all import *
>>> t.is_zero()
True
>>> t == Integer(0)
True
>>> t == M.tensor_module(Integer(1),Integer(1)).zero()  # the zero element of the module of all type-(1,1) tensors on M
True


The components are managed by the class Components:

sage: type(t.comp(e))
<class 'sage.tensor.modules.comp.Components'>

>>> from sage.all import *
>>> type(t.comp(e))
<class 'sage.tensor.modules.comp.Components'>


Only non-zero components are actually stored, in the dictionary _comp of class Components, whose keys are the indices:

sage: t.comp(e)._comp
{}
sage: t.set_comp(e)[0,0] = -3 ; t.set_comp(e)[1,2] = 2
sage: t.comp(e)._comp  # random output order (dictionary)
{(0, 0): -3, (1, 2): 2}
sage: t.display(e)
t = -3 e_0⊗e^0 + 2 e_1⊗e^2

>>> from sage.all import *
>>> t.comp(e)._comp
{}
>>> t.set_comp(e)[Integer(0),Integer(0)] = -Integer(3) ; t.set_comp(e)[Integer(1),Integer(2)] = Integer(2)
>>> t.comp(e)._comp  # random output order (dictionary)
{(0, 0): -3, (1, 2): 2}
>>> t.display(e)
t = -3 e_0⊗e^0 + 2 e_1⊗e^2


Further tests of the comparison operator:

sage: t.is_zero()
False
sage: t == 0
False
sage: t == M.tensor_module(1,1).zero()
False
sage: t1 = t.copy()
sage: t1 == t
True
sage: t1[2,0] = 4
sage: t1 == t
False

>>> from sage.all import *
>>> t.is_zero()
False
>>> t == Integer(0)
False
>>> t == M.tensor_module(Integer(1),Integer(1)).zero()
False
>>> t1 = t.copy()
>>> t1 == t
True
>>> t1[Integer(2),Integer(0)] = Integer(4)
>>> t1 == t
False


As a multilinear map $$M^* \times M \rightarrow \ZZ$$, the type-$$(1,1)$$ tensor t acts on pairs formed by a linear form and a module element:

sage: a = M.linear_form(name='a') ; a[:] = (2, 1, -3) ; a
Linear form a on the Rank-3 free module M over the Integer Ring
sage: b = M([1,-6,2], name='b') ; b
Element b of the Rank-3 free module M over the Integer Ring
sage: t(a,b)
-2

>>> from sage.all import *
>>> a = M.linear_form(name='a') ; a[:] = (Integer(2), Integer(1), -Integer(3)) ; a
Linear form a on the Rank-3 free module M over the Integer Ring
>>> b = M([Integer(1),-Integer(6),Integer(2)], name='b') ; b
Element b of the Rank-3 free module M over the Integer Ring
>>> t(a,b)
-2

class sage.tensor.modules.free_module_tensor.FreeModuleTensor(fmodule: FiniteRankFreeModule, tensor_type, name: str | None = None, latex_name: str | None = None, sym=None, antisym=None, parent=None)[source]#

Tensor over a free module of finite rank over a commutative ring.

This is a Sage element class, the corresponding parent class being TensorFreeModule.

INPUT:

• fmodule – free module $$M$$ of finite rank over a commutative ring $$R$$, as an instance of FiniteRankFreeModule

• tensor_type – pair (k, l) with k being the contravariant rank and l the covariant rank

• name – (default: None) name given to the tensor

• latex_name – (default: None) LaTeX symbol to denote the tensor; if none is provided, the LaTeX symbol is set to name

• sym – (default: None) a symmetry or a list of symmetries among the tensor arguments: each symmetry is described by a tuple containing the positions of the involved arguments, with the convention position=0 for the first argument. For instance:

• sym = (0,1) for a symmetry between the 1st and 2nd arguments;

• sym = [(0,2), (1,3,4)] for a symmetry between the 1st and 3rd arguments and a symmetry between the 2nd, 4th and 5th arguments.

• antisym – (default: None) antisymmetry or list of antisymmetries among the arguments, with the same convention as for sym

• parent – (default: None) some specific parent (e.g. exterior power for alternating forms); if None, fmodule.tensor_module(k,l) is used

EXAMPLES:

A tensor of type $$(1,1)$$ on a rank-3 free module over $$\ZZ$$:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: t = M.tensor((1,1), name='t') ; t
Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> t = M.tensor((Integer(1),Integer(1)), name='t') ; t
Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring


Tensors are Element objects whose parents are tensor free modules:

sage: t.parent()
Free module of type-(1,1) tensors on the
Rank-3 free module M over the Integer Ring
sage: t.parent() is M.tensor_module(1,1)
True

>>> from sage.all import *
>>> t.parent()
Free module of type-(1,1) tensors on the
Rank-3 free module M over the Integer Ring
>>> t.parent() is M.tensor_module(Integer(1),Integer(1))
True


Return the components of self w.r.t. a given module basis for assignment, keeping the components w.r.t. other bases.

To delete the components w.r.t. other bases, use the method set_comp() instead.

INPUT:

• basis – (default: None) basis in which the components are defined; if none is provided, the components are assumed to refer to the module’s default basis

Warning

If the tensor has already components in other bases, it is the user’s responsibility to make sure that the components to be added are consistent with them.

OUTPUT:

• components in the given basis, as an instance of the class Components; if such components did not exist previously, they are created

EXAMPLES:

Setting components of a type-$$(1,1)$$ tensor:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e')
sage: t = M.tensor((1,1), name='t')
sage: t.display()
t = -3 e_0⊗e^1
sage: t.display()
t = -3 e_0⊗e^1 + 2 e_1⊗e^2
2-indices components w.r.t. Basis (e_0,e_1,e_2) on the
Rank-3 free module M over the Integer Ring

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> e = M.basis('e')
>>> t = M.tensor((Integer(1),Integer(1)), name='t')
>>> t.display()
t = -3 e_0⊗e^1
>>> t.display()
t = -3 e_0⊗e^1 + 2 e_1⊗e^2
2-indices components w.r.t. Basis (e_0,e_1,e_2) on the
Rank-3 free module M over the Integer Ring


Adding components in a new basis:

sage: f =  M.basis('f')

>>> from sage.all import *
>>> f =  M.basis('f')


The components w.r.t. basis e have been kept:

sage: sorted(t._components, key=repr)
[Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring,
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]
sage: t.display(f)
t = 4 f_0⊗f^1
sage: t.display(e)
t = -3 e_0⊗e^1 + 2 e_1⊗e^2

>>> from sage.all import *
>>> sorted(t._components, key=repr)
[Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring,
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]
>>> t.display(f)
t = 4 f_0⊗f^1
>>> t.display(e)
t = -3 e_0⊗e^1 + 2 e_1⊗e^2


Since zero is an immutable element, its components cannot be changed:

sage: z = M.tensor_module(1, 1).zero()
Traceback (most recent call last):
...
ValueError: the components of an immutable element cannot be changed

>>> from sage.all import *
>>> z = M.tensor_module(Integer(1), Integer(1)).zero()
Traceback (most recent call last):
...
ValueError: the components of an immutable element cannot be changed

antisymmetrize(*pos, **kwargs)[source]#

Antisymmetrization over some arguments.

INPUT:

• pos – list of argument positions involved in the antisymmetrization (with the convention position=0 for the first argument); if none, the antisymmetrization is performed over all the arguments

• basis – (default: None) module basis with respect to which the component computation is to be performed; if none, the module’s default basis is used if the tensor field has already components in it; otherwise another basis w.r.t. which the tensor has components will be picked

OUTPUT:

EXAMPLES:

Antisymmetrization of a tensor of type $$(2,0)$$:

sage: M = FiniteRankFreeModule(QQ, 3, name='M')
sage: e = M.basis('e')
sage: t = M.tensor((2,0))
sage: t[:] = [[1,-2,3], [4,5,6], [7,8,-9]]
sage: s = t.antisymmetrize() ; s
Alternating contravariant tensor of degree 2 on the 3-dimensional
vector space M over the Rational Field
sage: s.symmetries()
no symmetry;  antisymmetry: (0, 1)
sage: t[:], s[:]
(
[ 1 -2  3]  [ 0 -3 -2]
[ 4  5  6]  [ 3  0 -1]
[ 7  8 -9], [ 2  1  0]
)
sage: all(s[i,j] == 1/2*(t[i,j]-t[j,i])   # Check:
....:     for i in range(3) for j in range(3))
True
sage: s.antisymmetrize() == s  # another test
True
sage: t.antisymmetrize() == t.antisymmetrize(0,1)
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(QQ, Integer(3), name='M')
>>> e = M.basis('e')
>>> t = M.tensor((Integer(2),Integer(0)))
>>> t[:] = [[Integer(1),-Integer(2),Integer(3)], [Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),-Integer(9)]]
>>> s = t.antisymmetrize() ; s
Alternating contravariant tensor of degree 2 on the 3-dimensional
vector space M over the Rational Field
>>> s.symmetries()
no symmetry;  antisymmetry: (0, 1)
>>> t[:], s[:]
(
[ 1 -2  3]  [ 0 -3 -2]
[ 4  5  6]  [ 3  0 -1]
[ 7  8 -9], [ 2  1  0]
)
>>> all(s[i,j] == Integer(1)/Integer(2)*(t[i,j]-t[j,i])   # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)))
True
>>> s.antisymmetrize() == s  # another test
True
>>> t.antisymmetrize() == t.antisymmetrize(Integer(0),Integer(1))
True


Antisymmetrization of a tensor of type $$(0, 3)$$ over the first two arguments:

sage: t = M.tensor((0,3))
sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]],
....:         [[10,-11,12], [13,14,-15], [16,17,18]],
....:         [[19,-20,-21], [-22,23,24], [25,26,-27]]]
sage: s = t.antisymmetrize(0,1) ; s  # (0,1) = the first two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
no symmetry;  antisymmetry: (0, 1)
sage: s[:]
[[[0, 0, 0], [-7, 8, -3], [-6, 14, 6]],
[[7, -8, 3], [0, 0, 0], [19, -3, -3]],
[[6, -14, -6], [-19, 3, 3], [0, 0, 0]]]
sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[j,i,k])   # Check:
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.antisymmetrize(0,1) == s  # another test
True
sage: s.symmetrize(0,1) == 0  # of course
True

>>> from sage.all import *
>>> t = M.tensor((Integer(0),Integer(3)))
>>> t[:] = [[[Integer(1),Integer(2),Integer(3)], [-Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),-Integer(9)]],
...         [[Integer(10),-Integer(11),Integer(12)], [Integer(13),Integer(14),-Integer(15)], [Integer(16),Integer(17),Integer(18)]],
...         [[Integer(19),-Integer(20),-Integer(21)], [-Integer(22),Integer(23),Integer(24)], [Integer(25),Integer(26),-Integer(27)]]]
>>> s = t.antisymmetrize(Integer(0),Integer(1)) ; s  # (0,1) = the first two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
no symmetry;  antisymmetry: (0, 1)
>>> s[:]
[[[0, 0, 0], [-7, 8, -3], [-6, 14, 6]],
[[7, -8, 3], [0, 0, 0], [19, -3, -3]],
[[6, -14, -6], [-19, 3, 3], [0, 0, 0]]]
>>> all(s[i,j,k] == Integer(1)/Integer(2)*(t[i,j,k]-t[j,i,k])   # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.antisymmetrize(Integer(0),Integer(1)) == s  # another test
True
>>> s.symmetrize(Integer(0),Integer(1)) == Integer(0)  # of course
True


Instead of invoking the method antisymmetrize(), one can use the index notation with square brackets denoting the antisymmetrization; it suffices to pass the indices as a string inside square brackets:

sage: s1 = t['_[ij]k'] ; s1
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s1.symmetries()
no symmetry;  antisymmetry: (0, 1)
sage: s1 == s
True

>>> from sage.all import *
>>> s1 = t['_[ij]k'] ; s1
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s1.symmetries()
no symmetry;  antisymmetry: (0, 1)
>>> s1 == s
True


The LaTeX notation is recognized:

sage: t['_{[ij]k}'] == s
True

>>> from sage.all import *
>>> t['_{[ij]k}'] == s
True


Note that in the index notation, the name of the indices is irrelevant; they can even be replaced by dots:

sage: t['_[..].'] == s
True

>>> from sage.all import *
>>> t['_[..].'] == s
True


Antisymmetrization of a tensor of type $$(0,3)$$ over the first and last arguments:

sage: s = t.antisymmetrize(0,2) ; s  # (0,2) = first and last arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
no symmetry;  antisymmetry: (0, 2)
sage: s[:]
[[[0, -4, -8], [0, -4, 14], [0, -4, -17]],
[[4, 0, 16], [4, 0, -19], [4, 0, -4]],
[[8, -16, 0], [-14, 19, 0], [17, 4, 0]]]
sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[k,j,i])   # Check:
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.antisymmetrize(0,2) == s  # another test
True
sage: s.symmetrize(0,2) == 0  # of course
True
sage: s.symmetrize(0,1) == 0  # no reason for this to hold
False

>>> from sage.all import *
>>> s = t.antisymmetrize(Integer(0),Integer(2)) ; s  # (0,2) = first and last arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
no symmetry;  antisymmetry: (0, 2)
>>> s[:]
[[[0, -4, -8], [0, -4, 14], [0, -4, -17]],
[[4, 0, 16], [4, 0, -19], [4, 0, -4]],
[[8, -16, 0], [-14, 19, 0], [17, 4, 0]]]
>>> all(s[i,j,k] == Integer(1)/Integer(2)*(t[i,j,k]-t[k,j,i])   # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.antisymmetrize(Integer(0),Integer(2)) == s  # another test
True
>>> s.symmetrize(Integer(0),Integer(2)) == Integer(0)  # of course
True
>>> s.symmetrize(Integer(0),Integer(1)) == Integer(0)  # no reason for this to hold
False


Antisymmetrization of a tensor of type $$(0,3)$$ over the last two arguments:

sage: s = t.antisymmetrize(1,2) ; s  # (1,2) = the last two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
no symmetry;  antisymmetry: (1, 2)
sage: s[:]
[[[0, 3, -2], [-3, 0, -1], [2, 1, 0]],
[[0, -12, -2], [12, 0, -16], [2, 16, 0]],
[[0, 1, -23], [-1, 0, -1], [23, 1, 0]]]
sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[i,k,j])   # Check:
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.antisymmetrize(1,2) == s  # another test
True
sage: s.symmetrize(1,2) == 0  # of course
True

>>> from sage.all import *
>>> s = t.antisymmetrize(Integer(1),Integer(2)) ; s  # (1,2) = the last two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
no symmetry;  antisymmetry: (1, 2)
>>> s[:]
[[[0, 3, -2], [-3, 0, -1], [2, 1, 0]],
[[0, -12, -2], [12, 0, -16], [2, 16, 0]],
[[0, 1, -23], [-1, 0, -1], [23, 1, 0]]]
>>> all(s[i,j,k] == Integer(1)/Integer(2)*(t[i,j,k]-t[i,k,j])   # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.antisymmetrize(Integer(1),Integer(2)) == s  # another test
True
>>> s.symmetrize(Integer(1),Integer(2)) == Integer(0)  # of course
True


The index notation can be used instead of the explicit call to antisymmetrize():

sage: t['_i[jk]'] == t.antisymmetrize(1,2)
True

>>> from sage.all import *
>>> t['_i[jk]'] == t.antisymmetrize(Integer(1),Integer(2))
True


Full antisymmetrization of a tensor of type $$(0,3)$$:

sage: s = t.antisymmetrize() ; s
Alternating form of degree 3 on the 3-dimensional vector space M
over the Rational Field
sage: s.symmetries()
no symmetry;  antisymmetry: (0, 1, 2)
sage: s[:]
[[[0, 0, 0], [0, 0, 2/3], [0, -2/3, 0]],
[[0, 0, -2/3], [0, 0, 0], [2/3, 0, 0]],
[[0, 2/3, 0], [-2/3, 0, 0], [0, 0, 0]]]
sage: all(s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k]
....:                      +t[k,i,j]-t[k,j,i])
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.antisymmetrize() == s  # another test
True
sage: s.symmetrize(0,1) == 0  # of course
True
sage: s.symmetrize(0,2) == 0  # of course
True
sage: s.symmetrize(1,2) == 0  # of course
True
sage: t.antisymmetrize() == t.antisymmetrize(0,1,2)
True

>>> from sage.all import *
>>> s = t.antisymmetrize() ; s
Alternating form of degree 3 on the 3-dimensional vector space M
over the Rational Field
>>> s.symmetries()
no symmetry;  antisymmetry: (0, 1, 2)
>>> s[:]
[[[0, 0, 0], [0, 0, 2/3], [0, -2/3, 0]],
[[0, 0, -2/3], [0, 0, 0], [2/3, 0, 0]],
[[0, 2/3, 0], [-2/3, 0, 0], [0, 0, 0]]]
>>> all(s[i,j,k] == Integer(1)/Integer(6)*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k]
...                      +t[k,i,j]-t[k,j,i])
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.antisymmetrize() == s  # another test
True
>>> s.symmetrize(Integer(0),Integer(1)) == Integer(0)  # of course
True
>>> s.symmetrize(Integer(0),Integer(2)) == Integer(0)  # of course
True
>>> s.symmetrize(Integer(1),Integer(2)) == Integer(0)  # of course
True
>>> t.antisymmetrize() == t.antisymmetrize(Integer(0),Integer(1),Integer(2))
True


The index notation can be used instead of the explicit call to antisymmetrize():

sage: t['_[ijk]'] == t.antisymmetrize()
True
sage: t['_[abc]'] == t.antisymmetrize()
True
sage: t['_[...]'] == t.antisymmetrize()
True
sage: t['_{[ijk]}'] == t.antisymmetrize() # LaTeX notation
True

>>> from sage.all import *
>>> t['_[ijk]'] == t.antisymmetrize()
True
>>> t['_[abc]'] == t.antisymmetrize()
True
>>> t['_[...]'] == t.antisymmetrize()
True
>>> t['_{[ijk]}'] == t.antisymmetrize() # LaTeX notation
True


Antisymmetrization can be performed only on arguments on the same type:

sage: t = M.tensor((1,2))
sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]],
....:         [[10,-11,12], [13,14,-15], [16,17,18]],
....:         [[19,-20,-21], [-22,23,24], [25,26,-27]]]
sage: s = t.antisymmetrize(0,1)
Traceback (most recent call last):
...
TypeError: 0 is a contravariant position, while 1 is a covariant position;
antisymmetrization is meaningful only on tensor arguments of the same type
sage: s = t.antisymmetrize(1,2) # OK: both 1 and 2 are covariant positions

>>> from sage.all import *
>>> t = M.tensor((Integer(1),Integer(2)))
>>> t[:] = [[[Integer(1),Integer(2),Integer(3)], [-Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),-Integer(9)]],
...         [[Integer(10),-Integer(11),Integer(12)], [Integer(13),Integer(14),-Integer(15)], [Integer(16),Integer(17),Integer(18)]],
...         [[Integer(19),-Integer(20),-Integer(21)], [-Integer(22),Integer(23),Integer(24)], [Integer(25),Integer(26),-Integer(27)]]]
>>> s = t.antisymmetrize(Integer(0),Integer(1))
Traceback (most recent call last):
...
TypeError: 0 is a contravariant position, while 1 is a covariant position;
antisymmetrization is meaningful only on tensor arguments of the same type
>>> s = t.antisymmetrize(Integer(1),Integer(2)) # OK: both 1 and 2 are covariant positions


The order of positions does not matter:

sage: t.antisymmetrize(2,1) == t.antisymmetrize(1,2)
True

>>> from sage.all import *
>>> t.antisymmetrize(Integer(2),Integer(1)) == t.antisymmetrize(Integer(1),Integer(2))
True


Again, the index notation can be used:

sage: t['^i_[jk]'] == t.antisymmetrize(1,2)
True
sage: t['^i_{[jk]}'] == t.antisymmetrize(1,2)  # LaTeX notation
True

>>> from sage.all import *
>>> t['^i_[jk]'] == t.antisymmetrize(Integer(1),Integer(2))
True
>>> t['^i_{[jk]}'] == t.antisymmetrize(Integer(1),Integer(2))  # LaTeX notation
True


The character ‘^’ can be skipped:

sage: t['i_[jk]'] == t.antisymmetrize(1,2)
True

>>> from sage.all import *
>>> t['i_[jk]'] == t.antisymmetrize(Integer(1),Integer(2))
True

base_module()[source]#

Return the module on which self is defined.

OUTPUT:

EXAMPLES:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: M.an_element().base_module()
Rank-3 free module M over the Integer Ring
sage: t = M.tensor((2,1))
sage: t.base_module()
Rank-3 free module M over the Integer Ring
sage: t.base_module() is M
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> M.an_element().base_module()
Rank-3 free module M over the Integer Ring
>>> t = M.tensor((Integer(2),Integer(1)))
>>> t.base_module()
Rank-3 free module M over the Integer Ring
>>> t.base_module() is M
True

common_basis(other)[source]#

Find a common basis for the components of self and other.

In case of multiple common bases, the free module’s default basis is privileged. If the current components of self and other are all relative to different bases, a common basis is searched by performing a component transformation, via the transformations listed in self._fmodule._basis_changes, still privileging transformations to the free module’s default basis.

INPUT:

OUTPUT:

EXAMPLES:

Common basis for the components of two module elements:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: u = M([2,1,-5])
sage: f = M.basis('f')
sage: M._basis_changes.clear() # to ensure that bases e and f are unrelated at this stage
sage: v = M([0,4,2], basis=f)
sage: u.common_basis(v)

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> u = M([Integer(2),Integer(1),-Integer(5)])
>>> f = M.basis('f')
>>> M._basis_changes.clear() # to ensure that bases e and f are unrelated at this stage
>>> v = M([Integer(0),Integer(4),Integer(2)], basis=f)
>>> u.common_basis(v)


The above result is None since u and v have been defined on different bases and no connection between these bases have been set:

sage: list(u._components)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring]
sage: list(v._components)
[Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]

>>> from sage.all import *
>>> list(u._components)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring]
>>> list(v._components)
[Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]


Linking bases e and f changes the result:

sage: a = M.automorphism()
sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]]
sage: M.set_change_of_basis(e, f, a)
sage: u.common_basis(v)
Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring

>>> from sage.all import *
>>> a = M.automorphism()
>>> a[:] = [[Integer(0),Integer(0),Integer(1)], [Integer(1),Integer(0),Integer(0)], [Integer(0),-Integer(1),Integer(0)]]
>>> M.set_change_of_basis(e, f, a)
>>> u.common_basis(v)
Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring


Indeed, v is now known in basis e:

sage: sorted(v._components, key=repr)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring,
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]

>>> from sage.all import *
>>> sorted(v._components, key=repr)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring,
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]

comp(basis=None, from_basis=None)[source]#

Return the components of self w.r.t to a given module basis.

If the components are not known already, they are computed by the tensor change-of-basis formula from components in another basis.

INPUT:

• basis – (default: None) basis in which the components are required; if none is provided, the components are assumed to refer to the module’s default basis

• from_basis – (default: None) basis from which the required components are computed, via the tensor change-of-basis formula, if they are not known already in the basis basis; if none, a basis from which both the components and a change-of-basis to basis are known is selected.

OUTPUT:

EXAMPLES:

Components of a tensor of type-$$(1,1)$$:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: t = M.tensor((1,1), name='t')
sage: t[1,2] = -3 ; t[3,3] = 2
sage: t.components()
2-indices components w.r.t. Basis (e_1,e_2,e_3)
on the Rank-3 free module M over the Integer Ring
sage: t.components() is t.components(e)  # since e is M's default basis
True
sage: t.components()[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> t = M.tensor((Integer(1),Integer(1)), name='t')
>>> t[Integer(1),Integer(2)] = -Integer(3) ; t[Integer(3),Integer(3)] = Integer(2)
>>> t.components()
2-indices components w.r.t. Basis (e_1,e_2,e_3)
on the Rank-3 free module M over the Integer Ring
>>> t.components() is t.components(e)  # since e is M's default basis
True
>>> t.components()[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]


A shortcut is t.comp():

sage: t.comp() is t.components()
True

>>> from sage.all import *
>>> t.comp() is t.components()
True


A direct access to the components w.r.t. the module’s default basis is provided by the square brackets applied to the tensor itself:

sage: t[1,2] is t.comp(e)[1,2]
True
sage: t[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]

>>> from sage.all import *
>>> t[Integer(1),Integer(2)] is t.comp(e)[Integer(1),Integer(2)]
True
>>> t[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]


Components computed via a change-of-basis formula:

sage: a = M.automorphism()
sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]]
sage: f = e.new_basis(a, 'f')
sage: t.comp(f)
2-indices components w.r.t. Basis (f_1,f_2,f_3)
on the Rank-3 free module M over the Integer Ring
sage: t.comp(f)[:]
[ 0  0  0]
[ 0  2  0]
[-3  0  0]

>>> from sage.all import *
>>> a = M.automorphism()
>>> a[:] = [[Integer(0),Integer(0),Integer(1)], [Integer(1),Integer(0),Integer(0)], [Integer(0),-Integer(1),Integer(0)]]
>>> f = e.new_basis(a, 'f')
>>> t.comp(f)
2-indices components w.r.t. Basis (f_1,f_2,f_3)
on the Rank-3 free module M over the Integer Ring
>>> t.comp(f)[:]
[ 0  0  0]
[ 0  2  0]
[-3  0  0]

components(basis=None, from_basis=None)[source]#

Return the components of self w.r.t to a given module basis.

If the components are not known already, they are computed by the tensor change-of-basis formula from components in another basis.

INPUT:

• basis – (default: None) basis in which the components are required; if none is provided, the components are assumed to refer to the module’s default basis

• from_basis – (default: None) basis from which the required components are computed, via the tensor change-of-basis formula, if they are not known already in the basis basis; if none, a basis from which both the components and a change-of-basis to basis are known is selected.

OUTPUT:

EXAMPLES:

Components of a tensor of type-$$(1,1)$$:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: t = M.tensor((1,1), name='t')
sage: t[1,2] = -3 ; t[3,3] = 2
sage: t.components()
2-indices components w.r.t. Basis (e_1,e_2,e_3)
on the Rank-3 free module M over the Integer Ring
sage: t.components() is t.components(e)  # since e is M's default basis
True
sage: t.components()[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> t = M.tensor((Integer(1),Integer(1)), name='t')
>>> t[Integer(1),Integer(2)] = -Integer(3) ; t[Integer(3),Integer(3)] = Integer(2)
>>> t.components()
2-indices components w.r.t. Basis (e_1,e_2,e_3)
on the Rank-3 free module M over the Integer Ring
>>> t.components() is t.components(e)  # since e is M's default basis
True
>>> t.components()[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]


A shortcut is t.comp():

sage: t.comp() is t.components()
True

>>> from sage.all import *
>>> t.comp() is t.components()
True


A direct access to the components w.r.t. the module’s default basis is provided by the square brackets applied to the tensor itself:

sage: t[1,2] is t.comp(e)[1,2]
True
sage: t[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]

>>> from sage.all import *
>>> t[Integer(1),Integer(2)] is t.comp(e)[Integer(1),Integer(2)]
True
>>> t[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]


Components computed via a change-of-basis formula:

sage: a = M.automorphism()
sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]]
sage: f = e.new_basis(a, 'f')
sage: t.comp(f)
2-indices components w.r.t. Basis (f_1,f_2,f_3)
on the Rank-3 free module M over the Integer Ring
sage: t.comp(f)[:]
[ 0  0  0]
[ 0  2  0]
[-3  0  0]

>>> from sage.all import *
>>> a = M.automorphism()
>>> a[:] = [[Integer(0),Integer(0),Integer(1)], [Integer(1),Integer(0),Integer(0)], [Integer(0),-Integer(1),Integer(0)]]
>>> f = e.new_basis(a, 'f')
>>> t.comp(f)
2-indices components w.r.t. Basis (f_1,f_2,f_3)
on the Rank-3 free module M over the Integer Ring
>>> t.comp(f)[:]
[ 0  0  0]
[ 0  2  0]
[-3  0  0]

contract(*args)[source]#

Contraction on one or more indices with another tensor.

INPUT:

• pos1 – positions of the indices in self involved in the contraction; pos1 must be a sequence of integers, with 0 standing for the first index position, 1 for the second one, etc; if pos1 is not provided, a single contraction on the last index position of self is assumed

• other – the tensor to contract with

• pos2 – positions of the indices in other involved in the contraction, with the same conventions as for pos1; if pos2 is not provided, a single contraction on the first index position of other is assumed

OUTPUT:

• tensor resulting from the contraction at the positions pos1 and pos2 of self with other

EXAMPLES:

Contraction of a tensor of type $$(0,1)$$ with a tensor of type $$(1,0)$$:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e')
sage: a = M.linear_form()  # tensor of type (0,1) is a linear form
sage: a[:] = [-3,2,1]
sage: b = M([2,5,-2])  # tensor of type (1,0) is a module element
sage: s = a.contract(b) ; s
2
sage: s in M.base_ring()
True
sage: s == a[0]*b[0] + a[1]*b[1] + a[2]*b[2]  # check of the computation
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> e = M.basis('e')
>>> a = M.linear_form()  # tensor of type (0,1) is a linear form
>>> a[:] = [-Integer(3),Integer(2),Integer(1)]
>>> b = M([Integer(2),Integer(5),-Integer(2)])  # tensor of type (1,0) is a module element
>>> s = a.contract(b) ; s
2
>>> s in M.base_ring()
True
>>> s == a[Integer(0)]*b[Integer(0)] + a[Integer(1)]*b[Integer(1)] + a[Integer(2)]*b[Integer(2)]  # check of the computation
True


The positions of the contraction indices can be set explicitly:

sage: s == a.contract(0, b, 0)
True
sage: s == a.contract(0, b)
True
sage: s == a.contract(b, 0)
True

>>> from sage.all import *
>>> s == a.contract(Integer(0), b, Integer(0))
True
>>> s == a.contract(Integer(0), b)
True
>>> s == a.contract(b, Integer(0))
True


Instead of the explicit call to the method contract(), the index notation can be used to specify the contraction, via Einstein convention (summation on repeated indices); it suffices to pass the indices as a string inside square brackets:

sage: s1 = a['_i']*b['^i'] ; s1
2
sage: s1 == s
True

>>> from sage.all import *
>>> s1 = a['_i']*b['^i'] ; s1
2
>>> s1 == s
True


In the present case, performing the contraction is identical to applying the linear form to the module element:

sage: a.contract(b) == a(b)
True

>>> from sage.all import *
>>> a.contract(b) == a(b)
True


or to applying the module element, considered as a tensor of type $$(1,0)$$, to the linear form:

sage: a.contract(b) == b(a)
True

>>> from sage.all import *
>>> a.contract(b) == b(a)
True


We have also:

sage: a.contract(b) == b.contract(a)
True

>>> from sage.all import *
>>> a.contract(b) == b.contract(a)
True


Contraction of a tensor of type $$(1,1)$$ with a tensor of type $$(1,0)$$:

sage: a = M.tensor((1,1))
sage: a[:] = [[-1,2,3],[4,-5,6],[7,8,9]]
sage: s = a.contract(b) ; s
Element of the Rank-3 free module M over the Integer Ring
sage: s.display()
2 e_0 - 29 e_1 + 36 e_2

>>> from sage.all import *
>>> a = M.tensor((Integer(1),Integer(1)))
>>> a[:] = [[-Integer(1),Integer(2),Integer(3)],[Integer(4),-Integer(5),Integer(6)],[Integer(7),Integer(8),Integer(9)]]
>>> s = a.contract(b) ; s
Element of the Rank-3 free module M over the Integer Ring
>>> s.display()
2 e_0 - 29 e_1 + 36 e_2


Since the index positions have not been specified, the contraction takes place on the last position of a (i.e. no. 1) and the first position of b (i.e. no. 0):

sage: a.contract(b) == a.contract(1, b, 0)
True
sage: a.contract(b) == b.contract(0, a, 1)
True
sage: a.contract(b) == b.contract(a, 1)
True

>>> from sage.all import *
>>> a.contract(b) == a.contract(Integer(1), b, Integer(0))
True
>>> a.contract(b) == b.contract(Integer(0), a, Integer(1))
True
>>> a.contract(b) == b.contract(a, Integer(1))
True


Using the index notation with Einstein convention:

sage: a['^i_j']*b['^j'] == a.contract(b)
True

>>> from sage.all import *
>>> a['^i_j']*b['^j'] == a.contract(b)
True


The index i can be replaced by a dot:

sage: a['^._j']*b['^j'] == a.contract(b)
True

>>> from sage.all import *
>>> a['^._j']*b['^j'] == a.contract(b)
True


and the symbol ^ may be omitted, the distinction between contravariant and covariant indices being the position with respect to the symbol _:

sage: a['._j']*b['j'] == a.contract(b)
True

>>> from sage.all import *
>>> a['._j']*b['j'] == a.contract(b)
True


Contraction is possible only between a contravariant index and a covariant one:

sage: a.contract(0, b)
Traceback (most recent call last):
...
TypeError: contraction on two contravariant indices not permitted

>>> from sage.all import *
>>> a.contract(Integer(0), b)
Traceback (most recent call last):
...
TypeError: contraction on two contravariant indices not permitted


Contraction of a tensor of type $$(2,1)$$ with a tensor of type $$(0,2)$$:

sage: a = a*b ; a
Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring
sage: b = M.tensor((0,2))
sage: b[:] = [[-2,3,1], [0,-2,3], [4,-7,6]]
sage: s = a.contract(1, b, 1) ; s
Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring
sage: s[:]
[[[-9, 16, 39], [18, -32, -78], [27, -48, -117]],
[[36, -64, -156], [-45, 80, 195], [54, -96, -234]],
[[63, -112, -273], [72, -128, -312], [81, -144, -351]]]

>>> from sage.all import *
>>> a = a*b ; a
Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring
>>> b = M.tensor((Integer(0),Integer(2)))
>>> b[:] = [[-Integer(2),Integer(3),Integer(1)], [Integer(0),-Integer(2),Integer(3)], [Integer(4),-Integer(7),Integer(6)]]
>>> s = a.contract(Integer(1), b, Integer(1)) ; s
Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring
>>> s[:]
[[[-9, 16, 39], [18, -32, -78], [27, -48, -117]],
[[36, -64, -156], [-45, 80, 195], [54, -96, -234]],
[[63, -112, -273], [72, -128, -312], [81, -144, -351]]]


Check of the computation:

sage: all(s[i,j,k] == a[i,0,j]*b[k,0]+a[i,1,j]*b[k,1]+a[i,2,j]*b[k,2]
....:     for i in range(3) for j in range(3) for k in range(3))
True

>>> from sage.all import *
>>> all(s[i,j,k] == a[i,Integer(0),j]*b[k,Integer(0)]+a[i,Integer(1),j]*b[k,Integer(1)]+a[i,Integer(2),j]*b[k,Integer(2)]
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True


Using index notation:

sage: a['il_j']*b['_kl'] == a.contract(1, b, 1)
True

>>> from sage.all import *
>>> a['il_j']*b['_kl'] == a.contract(Integer(1), b, Integer(1))
True


LaTeX notation are allowed:

sage: a['^{il}_j']*b['_{kl}'] == a.contract(1, b, 1)
True

>>> from sage.all import *
>>> a['^{il}_j']*b['_{kl}'] == a.contract(Integer(1), b, Integer(1))
True


Indices not involved in the contraction may be replaced by dots:

sage: a['.l_.']*b['_.l'] == a.contract(1, b, 1)
True

>>> from sage.all import *
>>> a['.l_.']*b['_.l'] == a.contract(Integer(1), b, Integer(1))
True


The two tensors do not have to be defined on the same basis for the contraction to take place, reflecting the fact that the contraction is basis-independent:

sage: A = M.automorphism()
sage: A[:] =  [[0,0,1], [1,0,0], [0,-1,0]]
sage: h = e.new_basis(A, 'h')
sage: b.comp(h)[:]  # forces the computation of b's components w.r.t. basis h
[-2 -3  0]
[ 7  6 -4]
[ 3 -1 -2]
sage: b.del_other_comp(h)  # deletes components w.r.t. basis e
sage: list(b._components)  # indeed:
[Basis (h_0,h_1,h_2) on the Rank-3 free module M over the Integer Ring]
sage: list(a._components)  # while a is known only in basis e:
[Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring]
sage: s1 = a.contract(1, b, 1) ; s1  # yet the computation is possible
Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring
sage: s1 == s  # ... and yields the same result as previously:
True

>>> from sage.all import *
>>> A = M.automorphism()
>>> A[:] =  [[Integer(0),Integer(0),Integer(1)], [Integer(1),Integer(0),Integer(0)], [Integer(0),-Integer(1),Integer(0)]]
>>> h = e.new_basis(A, 'h')
>>> b.comp(h)[:]  # forces the computation of b's components w.r.t. basis h
[-2 -3  0]
[ 7  6 -4]
[ 3 -1 -2]
>>> b.del_other_comp(h)  # deletes components w.r.t. basis e
>>> list(b._components)  # indeed:
[Basis (h_0,h_1,h_2) on the Rank-3 free module M over the Integer Ring]
>>> list(a._components)  # while a is known only in basis e:
[Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring]
>>> s1 = a.contract(Integer(1), b, Integer(1)) ; s1  # yet the computation is possible
Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring
>>> s1 == s  # ... and yields the same result as previously:
True


The contraction can be performed on more than a single index; for instance a $$2$$-indices contraction of a type-$$(2,1)$$ tensor with a type-$$(1,2)$$ one is:

sage: a  # a is a tensor of type-(2,1)
Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring
sage: b = M([1,-1,2])*b ; b # a tensor of type (1,2)
Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring
sage: s = a.contract(1,2,b,1,0) ; s # the double contraction
Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring
sage: s[:]
[ -36   30   15]
[-252  210  105]
[-204  170   85]
sage: s == a['^.k_l']*b['^l_k.']  # the same thing in index notation
True

>>> from sage.all import *
>>> a  # a is a tensor of type-(2,1)
Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring
>>> b = M([Integer(1),-Integer(1),Integer(2)])*b ; b # a tensor of type (1,2)
Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring
>>> s = a.contract(Integer(1),Integer(2),b,Integer(1),Integer(0)) ; s # the double contraction
Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring
>>> s[:]
[ -36   30   15]
[-252  210  105]
[-204  170   85]
>>> s == a['^.k_l']*b['^l_k.']  # the same thing in index notation
True

copy(name=None, latex_name=None)[source]#

Return an exact copy of self.

The name and the derived quantities are not copied.

INPUT:

• name – (default: None) name given to the copy

• latex_name – (default: None) LaTeX symbol to denote the copy; if none is provided, the LaTeX symbol is set to name

EXAMPLES:

Copy of a tensor of type $$(1,1)$$:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: t = M.tensor((1,1), name='t')
sage: t[1,2] = -3 ; t[3,3] = 2
sage: t1 = t.copy()
sage: t1[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
sage: t1 == t
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> t = M.tensor((Integer(1),Integer(1)), name='t')
>>> t[Integer(1),Integer(2)] = -Integer(3) ; t[Integer(3),Integer(3)] = Integer(2)
>>> t1 = t.copy()
>>> t1[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
>>> t1 == t
True


If the original tensor is modified, the copy is not:

sage: t[2,2] = 4
sage: t1[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
sage: t1 == t
False

>>> from sage.all import *
>>> t[Integer(2),Integer(2)] = Integer(4)
>>> t1[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
>>> t1 == t
False

copy_from(other)[source]#

Make self to a copy from other.

INPUT:

• other – other tensor in the very same module from which self should be a copy of

Warning

All previous defined components will be deleted!

EXAMPLES:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: t = M.tensor((1,1), name='t')
sage: t[1,2] = -3 ; t[3,3] = 2
sage: s = M.tensor((1,1), name='s')
sage: s.copy_from(t)
sage: s[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
sage: s == t
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> t = M.tensor((Integer(1),Integer(1)), name='t')
>>> t[Integer(1),Integer(2)] = -Integer(3) ; t[Integer(3),Integer(3)] = Integer(2)
>>> s = M.tensor((Integer(1),Integer(1)), name='s')
>>> s.copy_from(t)
>>> s[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
>>> s == t
True


If the original tensor is modified, the copy is not:

sage: t[2,2] = 4
sage: s[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
sage: s == t
False

>>> from sage.all import *
>>> t[Integer(2),Integer(2)] = Integer(4)
>>> s[:]
[ 0 -3  0]
[ 0  0  0]
[ 0  0  2]
>>> s == t
False

del_other_comp(basis=None)[source]#

Delete all the components but those corresponding to basis.

INPUT:

• basis – (default: None) basis in which the components are kept; if none the module’s default basis is assumed

EXAMPLES:

Deleting components of a module element:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: u = M([2,1,-5])
sage: f = M.basis('f')
sage: sorted(u._components, key=repr)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring,
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
sage: u.del_other_comp(f)
sage: list(u._components)
[Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> u = M([Integer(2),Integer(1),-Integer(5)])
>>> f = M.basis('f')
>>> sorted(u._components, key=repr)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring,
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
>>> u.del_other_comp(f)
>>> list(u._components)
[Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]


Let us restore the components w.r.t. e and delete those w.r.t. f:

sage: u.add_comp(e)[:] = [2,1,-5]
sage: sorted(u._components, key=repr)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring,
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
sage: u.del_other_comp()  # default argument: basis = e
sage: list(u._components)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring]

>>> from sage.all import *
>>> sorted(u._components, key=repr)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring,
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
>>> u.del_other_comp()  # default argument: basis = e
>>> list(u._components)
[Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring]

disp(basis=None, format_spec=None)[source]#

Display self in terms of its expansion w.r.t. a given module basis.

The expansion is actually performed onto tensor products of elements of the given basis and of elements of its dual basis (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode).

INPUT:

• basis – (default: None) basis of the free module with respect to which the tensor is expanded; if none is provided, the module’s default basis is assumed

• format_spec – (default: None) format specification passed to self._fmodule._output_formatter to format the output

EXAMPLES:

Display of a module element (type-$$(1,0)$$ tensor):

sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1)
sage: e = M.basis('e') ; e
Basis (e_1,e_2) on the 2-dimensional vector space M over the
Rational Field
sage: v = M([1/3,-2], name='v')
sage: v.display(e)
v = 1/3 e_1 - 2 e_2
sage: v.display()  # a shortcut since e is M's default basis
v = 1/3 e_1 - 2 e_2
sage: latex(v.display())  # display in the notebook
v = \frac{1}{3} e_{1} -2 e_{2}

>>> from sage.all import *
>>> M = FiniteRankFreeModule(QQ, Integer(2), name='M', start_index=Integer(1))
>>> e = M.basis('e') ; e
Basis (e_1,e_2) on the 2-dimensional vector space M over the
Rational Field
>>> v = M([Integer(1)/Integer(3),-Integer(2)], name='v')
>>> v.display(e)
v = 1/3 e_1 - 2 e_2
>>> v.display()  # a shortcut since e is M's default basis
v = 1/3 e_1 - 2 e_2
>>> latex(v.display())  # display in the notebook
v = \frac{1}{3} e_{1} -2 e_{2}


A shortcut is disp():

sage: v.disp()
v = 1/3 e_1 - 2 e_2

>>> from sage.all import *
>>> v.disp()
v = 1/3 e_1 - 2 e_2


Display of a linear form (type-$$(0,1)$$ tensor):

sage: de = e.dual_basis() ; de
Dual basis (e^1,e^2) on the 2-dimensional vector space M over the
Rational Field
sage: w = - 3/4 * de[1] + de[2] ; w
Linear form on the 2-dimensional vector space M over the Rational
Field
sage: w.set_name('w', latex_name='\\omega')
sage: w.display()
w = -3/4 e^1 + e^2
sage: latex(w.display())  # display in the notebook
\omega = -\frac{3}{4} e^{1} +e^{2}

>>> from sage.all import *
>>> de = e.dual_basis() ; de
Dual basis (e^1,e^2) on the 2-dimensional vector space M over the
Rational Field
>>> w = - Integer(3)/Integer(4) * de[Integer(1)] + de[Integer(2)] ; w
Linear form on the 2-dimensional vector space M over the Rational
Field
>>> w.set_name('w', latex_name='\\omega')
>>> w.display()
w = -3/4 e^1 + e^2
>>> latex(w.display())  # display in the notebook
\omega = -\frac{3}{4} e^{1} +e^{2}


Display of a type-$$(1,1)$$ tensor:

sage: t = v*w ; t  # the type-(1,1) is formed as the tensor product of v by w
Type-(1,1) tensor v⊗w on the 2-dimensional vector space M over the
Rational Field
sage: t.display()
v⊗w = -1/4 e_1⊗e^1 + 1/3 e_1⊗e^2 + 3/2 e_2⊗e^1 - 2 e_2⊗e^2
sage: latex(t.display())  # display in the notebook
v\otimes \omega = -\frac{1}{4} e_{1}\otimes e^{1} +
\frac{1}{3} e_{1}\otimes e^{2} + \frac{3}{2} e_{2}\otimes e^{1}
-2 e_{2}\otimes e^{2}

>>> from sage.all import *
>>> t = v*w ; t  # the type-(1,1) is formed as the tensor product of v by w
Type-(1,1) tensor v⊗w on the 2-dimensional vector space M over the
Rational Field
>>> t.display()
v⊗w = -1/4 e_1⊗e^1 + 1/3 e_1⊗e^2 + 3/2 e_2⊗e^1 - 2 e_2⊗e^2
>>> latex(t.display())  # display in the notebook
v\otimes \omega = -\frac{1}{4} e_{1}\otimes e^{1} +
\frac{1}{3} e_{1}\otimes e^{2} + \frac{3}{2} e_{2}\otimes e^{1}
-2 e_{2}\otimes e^{2}


Display in a basis which is not the default one:

sage: a = M.automorphism(matrix=[[1,2],[3,4]], basis=e)
sage: f = e.new_basis(a, 'f')
sage: v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a
v = -8/3 f_1 + 3/2 f_2
sage: w.display(f)
w = 9/4 f^1 + 5/2 f^2
sage: t.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2

>>> from sage.all import *
>>> a = M.automorphism(matrix=[[Integer(1),Integer(2)],[Integer(3),Integer(4)]], basis=e)
>>> f = e.new_basis(a, 'f')
>>> v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a
v = -8/3 f_1 + 3/2 f_2
>>> w.display(f)
w = 9/4 f^1 + 5/2 f^2
>>> t.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2


Parallel computation:

sage: Parallelism().set('tensor', nproc=2)
sage: t2 = v*w
sage: t2.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2
sage: t2[f,:] == t[f,:]  # check of the parallel computation
True
sage: Parallelism().set('tensor', nproc=1)  # switch off parallelization

>>> from sage.all import *
>>> Parallelism().set('tensor', nproc=Integer(2))
>>> t2 = v*w
>>> t2.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2
>>> t2[f,:] == t[f,:]  # check of the parallel computation
True
>>> Parallelism().set('tensor', nproc=Integer(1))  # switch off parallelization


The output format can be set via the argument output_formatter passed at the module construction:

sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1,
....:                   output_formatter=Rational.numerical_approx)
sage: e = N.basis('e')
sage: v = N([1/3,-2], name='v')
sage: v.display()  # default format (53 bits of precision)
v = 0.333333333333333 e_1 - 2.00000000000000 e_2
sage: latex(v.display())
v = 0.333333333333333 e_{1} -2.00000000000000 e_{2}

>>> from sage.all import *
>>> N = FiniteRankFreeModule(QQ, Integer(2), name='N', start_index=Integer(1),
...                   output_formatter=Rational.numerical_approx)
>>> e = N.basis('e')
>>> v = N([Integer(1)/Integer(3),-Integer(2)], name='v')
>>> v.display()  # default format (53 bits of precision)
v = 0.333333333333333 e_1 - 2.00000000000000 e_2
>>> latex(v.display())
v = 0.333333333333333 e_{1} -2.00000000000000 e_{2}


The output format is then controlled by the argument format_spec of the method display():

sage: v.display(format_spec=10)  # 10 bits of precision
v = 0.33 e_1 - 2.0 e_2

>>> from sage.all import *
>>> v.display(format_spec=Integer(10))  # 10 bits of precision
v = 0.33 e_1 - 2.0 e_2


Check that the bug reported in Issue #22520 is fixed:

sage: # needs sage.symbolic
sage: M = FiniteRankFreeModule(SR, 3, name='M')
sage: e = M.basis('e')
sage: t = SR.var('t', domain='real')
sage: (t*e[0]).display()
t e_0

>>> from sage.all import *
>>> # needs sage.symbolic
>>> M = FiniteRankFreeModule(SR, Integer(3), name='M')
>>> e = M.basis('e')
>>> t = SR.var('t', domain='real')
>>> (t*e[Integer(0)]).display()
t e_0

display(basis=None, format_spec=None)[source]#

Display self in terms of its expansion w.r.t. a given module basis.

The expansion is actually performed onto tensor products of elements of the given basis and of elements of its dual basis (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode).

INPUT:

• basis – (default: None) basis of the free module with respect to which the tensor is expanded; if none is provided, the module’s default basis is assumed

• format_spec – (default: None) format specification passed to self._fmodule._output_formatter to format the output

EXAMPLES:

Display of a module element (type-$$(1,0)$$ tensor):

sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1)
sage: e = M.basis('e') ; e
Basis (e_1,e_2) on the 2-dimensional vector space M over the
Rational Field
sage: v = M([1/3,-2], name='v')
sage: v.display(e)
v = 1/3 e_1 - 2 e_2
sage: v.display()  # a shortcut since e is M's default basis
v = 1/3 e_1 - 2 e_2
sage: latex(v.display())  # display in the notebook
v = \frac{1}{3} e_{1} -2 e_{2}

>>> from sage.all import *
>>> M = FiniteRankFreeModule(QQ, Integer(2), name='M', start_index=Integer(1))
>>> e = M.basis('e') ; e
Basis (e_1,e_2) on the 2-dimensional vector space M over the
Rational Field
>>> v = M([Integer(1)/Integer(3),-Integer(2)], name='v')
>>> v.display(e)
v = 1/3 e_1 - 2 e_2
>>> v.display()  # a shortcut since e is M's default basis
v = 1/3 e_1 - 2 e_2
>>> latex(v.display())  # display in the notebook
v = \frac{1}{3} e_{1} -2 e_{2}


A shortcut is disp():

sage: v.disp()
v = 1/3 e_1 - 2 e_2

>>> from sage.all import *
>>> v.disp()
v = 1/3 e_1 - 2 e_2


Display of a linear form (type-$$(0,1)$$ tensor):

sage: de = e.dual_basis() ; de
Dual basis (e^1,e^2) on the 2-dimensional vector space M over the
Rational Field
sage: w = - 3/4 * de[1] + de[2] ; w
Linear form on the 2-dimensional vector space M over the Rational
Field
sage: w.set_name('w', latex_name='\\omega')
sage: w.display()
w = -3/4 e^1 + e^2
sage: latex(w.display())  # display in the notebook
\omega = -\frac{3}{4} e^{1} +e^{2}

>>> from sage.all import *
>>> de = e.dual_basis() ; de
Dual basis (e^1,e^2) on the 2-dimensional vector space M over the
Rational Field
>>> w = - Integer(3)/Integer(4) * de[Integer(1)] + de[Integer(2)] ; w
Linear form on the 2-dimensional vector space M over the Rational
Field
>>> w.set_name('w', latex_name='\\omega')
>>> w.display()
w = -3/4 e^1 + e^2
>>> latex(w.display())  # display in the notebook
\omega = -\frac{3}{4} e^{1} +e^{2}


Display of a type-$$(1,1)$$ tensor:

sage: t = v*w ; t  # the type-(1,1) is formed as the tensor product of v by w
Type-(1,1) tensor v⊗w on the 2-dimensional vector space M over the
Rational Field
sage: t.display()
v⊗w = -1/4 e_1⊗e^1 + 1/3 e_1⊗e^2 + 3/2 e_2⊗e^1 - 2 e_2⊗e^2
sage: latex(t.display())  # display in the notebook
v\otimes \omega = -\frac{1}{4} e_{1}\otimes e^{1} +
\frac{1}{3} e_{1}\otimes e^{2} + \frac{3}{2} e_{2}\otimes e^{1}
-2 e_{2}\otimes e^{2}

>>> from sage.all import *
>>> t = v*w ; t  # the type-(1,1) is formed as the tensor product of v by w
Type-(1,1) tensor v⊗w on the 2-dimensional vector space M over the
Rational Field
>>> t.display()
v⊗w = -1/4 e_1⊗e^1 + 1/3 e_1⊗e^2 + 3/2 e_2⊗e^1 - 2 e_2⊗e^2
>>> latex(t.display())  # display in the notebook
v\otimes \omega = -\frac{1}{4} e_{1}\otimes e^{1} +
\frac{1}{3} e_{1}\otimes e^{2} + \frac{3}{2} e_{2}\otimes e^{1}
-2 e_{2}\otimes e^{2}


Display in a basis which is not the default one:

sage: a = M.automorphism(matrix=[[1,2],[3,4]], basis=e)
sage: f = e.new_basis(a, 'f')
sage: v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a
v = -8/3 f_1 + 3/2 f_2
sage: w.display(f)
w = 9/4 f^1 + 5/2 f^2
sage: t.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2

>>> from sage.all import *
>>> a = M.automorphism(matrix=[[Integer(1),Integer(2)],[Integer(3),Integer(4)]], basis=e)
>>> f = e.new_basis(a, 'f')
>>> v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a
v = -8/3 f_1 + 3/2 f_2
>>> w.display(f)
w = 9/4 f^1 + 5/2 f^2
>>> t.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2


Parallel computation:

sage: Parallelism().set('tensor', nproc=2)
sage: t2 = v*w
sage: t2.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2
sage: t2[f,:] == t[f,:]  # check of the parallel computation
True
sage: Parallelism().set('tensor', nproc=1)  # switch off parallelization

>>> from sage.all import *
>>> Parallelism().set('tensor', nproc=Integer(2))
>>> t2 = v*w
>>> t2.display(f)
v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2
>>> t2[f,:] == t[f,:]  # check of the parallel computation
True
>>> Parallelism().set('tensor', nproc=Integer(1))  # switch off parallelization


The output format can be set via the argument output_formatter passed at the module construction:

sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1,
....:                   output_formatter=Rational.numerical_approx)
sage: e = N.basis('e')
sage: v = N([1/3,-2], name='v')
sage: v.display()  # default format (53 bits of precision)
v = 0.333333333333333 e_1 - 2.00000000000000 e_2
sage: latex(v.display())
v = 0.333333333333333 e_{1} -2.00000000000000 e_{2}

>>> from sage.all import *
>>> N = FiniteRankFreeModule(QQ, Integer(2), name='N', start_index=Integer(1),
...                   output_formatter=Rational.numerical_approx)
>>> e = N.basis('e')
>>> v = N([Integer(1)/Integer(3),-Integer(2)], name='v')
>>> v.display()  # default format (53 bits of precision)
v = 0.333333333333333 e_1 - 2.00000000000000 e_2
>>> latex(v.display())
v = 0.333333333333333 e_{1} -2.00000000000000 e_{2}


The output format is then controlled by the argument format_spec of the method display():

sage: v.display(format_spec=10)  # 10 bits of precision
v = 0.33 e_1 - 2.0 e_2

>>> from sage.all import *
>>> v.display(format_spec=Integer(10))  # 10 bits of precision
v = 0.33 e_1 - 2.0 e_2


Check that the bug reported in Issue #22520 is fixed:

sage: # needs sage.symbolic
sage: M = FiniteRankFreeModule(SR, 3, name='M')
sage: e = M.basis('e')
sage: t = SR.var('t', domain='real')
sage: (t*e[0]).display()
t e_0

>>> from sage.all import *
>>> # needs sage.symbolic
>>> M = FiniteRankFreeModule(SR, Integer(3), name='M')
>>> e = M.basis('e')
>>> t = SR.var('t', domain='real')
>>> (t*e[Integer(0)]).display()
t e_0

display_comp(basis=None, format_spec=None, symbol=None, latex_symbol=None, index_labels=None, index_latex_labels=None, only_nonzero=True, only_nonredundant=False)[source]#

Display the tensor components with respect to a given module basis, one per line.

The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode).

INPUT:

• basis – (default: None) basis of the free module with respect to which the tensor components are defined; if None, the module’s default basis is assumed

• format_spec – (default: None) format specification passed to self._fmodule._output_formatter to format the output

• symbol – (default: None) string (typically a single letter) specifying the symbol for the components; if None, the tensor name is used if it has been set, otherwise 'X' is used

• latex_symbol – (default: None) string specifying the LaTeX symbol for the components; if None, the tensor LaTeX name is used if it has been set, otherwise 'X' is used

• index_labels – (default: None) list of strings representing the labels of each of the individual indices; if None, integer labels are used

• index_latex_labels – (default: None) list of strings representing the LaTeX labels of each of the individual indices; if None, integers labels are used

• only_nonzero – (default: True) boolean; if True, only nonzero components are displayed

• only_nonredundant – (default: False) boolean; if True, only nonredundant components are displayed in case of symmetries

EXAMPLES:

Display of the components of a type-$$(2,1)$$ tensor on a rank 2 vector space over $$\QQ$$:

sage: FiniteRankFreeModule._clear_cache_() # for doctests only
sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1)
sage: e = M.basis('e')
sage: t = M.tensor((2,1), name='T', sym=(0,1))
sage: t[1,2,1], t[1,2,2], t[2,2,2] = 2/3, -1/4, 3
sage: t.display()
T = 2/3 e_1⊗e_2⊗e^1 - 1/4 e_1⊗e_2⊗e^2 + 2/3 e_2⊗e_1⊗e^1
- 1/4 e_2⊗e_1⊗e^2 + 3 e_2⊗e_2⊗e^2
sage: t.display_comp()
T^12_1 = 2/3
T^12_2 = -1/4
T^21_1 = 2/3
T^21_2 = -1/4
T^22_2 = 3

>>> from sage.all import *
>>> FiniteRankFreeModule._clear_cache_() # for doctests only
>>> M = FiniteRankFreeModule(QQ, Integer(2), name='M', start_index=Integer(1))
>>> e = M.basis('e')
>>> t = M.tensor((Integer(2),Integer(1)), name='T', sym=(Integer(0),Integer(1)))
>>> t[Integer(1),Integer(2),Integer(1)], t[Integer(1),Integer(2),Integer(2)], t[Integer(2),Integer(2),Integer(2)] = Integer(2)/Integer(3), -Integer(1)/Integer(4), Integer(3)
>>> t.display()
T = 2/3 e_1⊗e_2⊗e^1 - 1/4 e_1⊗e_2⊗e^2 + 2/3 e_2⊗e_1⊗e^1
- 1/4 e_2⊗e_1⊗e^2 + 3 e_2⊗e_2⊗e^2
>>> t.display_comp()
T^12_1 = 2/3
T^12_2 = -1/4
T^21_1 = 2/3
T^21_2 = -1/4
T^22_2 = 3


The LaTeX output for the notebook:

sage: latex(t.display_comp())
\begin{array}{lcl} {T}_{\phantom{\, 1}\phantom{\, 2}\,1}^{\,1\,2\phantom{\, 1}}
& = & \frac{2}{3} \\ {T}_{\phantom{\, 1}\phantom{\, 2}\,2}^{\,1\,2\phantom{\, 2}}
& = & -\frac{1}{4} \\ {T}_{\phantom{\, 2}\phantom{\, 1}\,1}^{\,2\,1\phantom{\, 1}}
& = & \frac{2}{3} \\ {T}_{\phantom{\, 2}\phantom{\, 1}\,2}^{\,2\,1\phantom{\, 2}}
& = & -\frac{1}{4} \\ {T}_{\phantom{\, 2}\phantom{\, 2}\,2}^{\,2\,2\phantom{\, 2}}
& = & 3 \end{array}

>>> from sage.all import *
>>> latex(t.display_comp())
\begin{array}{lcl} {T}_{\phantom{\, 1}\phantom{\, 2}\,1}^{\,1\,2\phantom{\, 1}}
& = & \frac{2}{3} \\ {T}_{\phantom{\, 1}\phantom{\, 2}\,2}^{\,1\,2\phantom{\, 2}}
& = & -\frac{1}{4} \\ {T}_{\phantom{\, 2}\phantom{\, 1}\,1}^{\,2\,1\phantom{\, 1}}
& = & \frac{2}{3} \\ {T}_{\phantom{\, 2}\phantom{\, 1}\,2}^{\,2\,1\phantom{\, 2}}
& = & -\frac{1}{4} \\ {T}_{\phantom{\, 2}\phantom{\, 2}\,2}^{\,2\,2\phantom{\, 2}}
& = & 3 \end{array}


By default, only the non-vanishing components are displayed; to see all the components, the argument only_nonzero must be set to False:

sage: t.display_comp(only_nonzero=False)
T^11_1 = 0
T^11_2 = 0
T^12_1 = 2/3
T^12_2 = -1/4
T^21_1 = 2/3
T^21_2 = -1/4
T^22_1 = 0
T^22_2 = 3

>>> from sage.all import *
>>> t.display_comp(only_nonzero=False)
T^11_1 = 0
T^11_2 = 0
T^12_1 = 2/3
T^12_2 = -1/4
T^21_1 = 2/3
T^21_2 = -1/4
T^22_1 = 0
T^22_2 = 3


t being symmetric w.r.t. to its first two indices, one may ask to skip the components that can be deduced by symmetry:

sage: t.display_comp(only_nonredundant=True)
T^12_1 = 2/3
T^12_2 = -1/4
T^22_2 = 3

>>> from sage.all import *
>>> t.display_comp(only_nonredundant=True)
T^12_1 = 2/3
T^12_2 = -1/4
T^22_2 = 3


The index symbols can be customized:

sage: t.display_comp(index_labels=['x', 'y'])
T^xy_x = 2/3
T^xy_y = -1/4
T^yx_x = 2/3
T^yx_y = -1/4
T^yy_y = 3

>>> from sage.all import *
>>> t.display_comp(index_labels=['x', 'y'])
T^xy_x = 2/3
T^xy_y = -1/4
T^yx_x = 2/3
T^yx_y = -1/4
T^yy_y = 3


Display of the components w.r.t. a basis different from the default one:

sage: f = M.basis('f', from_family=(-e[1]+e[2], e[1]+e[2]))
sage: t.display_comp(basis=f)
T^11_1 = 29/24
T^11_2 = 13/24
T^12_1 = 3/4
T^12_2 = 3/4
T^21_1 = 3/4
T^21_2 = 3/4
T^22_1 = 7/24
T^22_2 = 23/24

>>> from sage.all import *
>>> f = M.basis('f', from_family=(-e[Integer(1)]+e[Integer(2)], e[Integer(1)]+e[Integer(2)]))
>>> t.display_comp(basis=f)
T^11_1 = 29/24
T^11_2 = 13/24
T^12_1 = 3/4
T^12_2 = 3/4
T^21_1 = 3/4
T^21_2 = 3/4
T^22_1 = 7/24
T^22_2 = 23/24

pick_a_basis()[source]#

Return a basis in which the tensor components are defined.

The free module’s default basis is privileged.

OUTPUT:

EXAMPLES:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: t = M.tensor((2,0), name='t')
sage: e = M.basis('e')
sage: t[0,1] = 4  # component set in the default basis (e)
sage: t.pick_a_basis()
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
sage: f = M.basis('f')
sage: t.add_comp(f)[2,1] = -4  # the components in basis e are not erased
sage: t.pick_a_basis()
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
sage: t.set_comp(f)[2,1] = -4  # the components in basis e not erased
sage: t.pick_a_basis()
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> t = M.tensor((Integer(2),Integer(0)), name='t')
>>> e = M.basis('e')
>>> t[Integer(0),Integer(1)] = Integer(4)  # component set in the default basis (e)
>>> t.pick_a_basis()
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
>>> f = M.basis('f')
>>> t.add_comp(f)[Integer(2),Integer(1)] = -Integer(4)  # the components in basis e are not erased
>>> t.pick_a_basis()
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
>>> t.set_comp(f)[Integer(2),Integer(1)] = -Integer(4)  # the components in basis e not erased
>>> t.pick_a_basis()
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring

set_comp(basis=None)[source]#

Return the components of self w.r.t. a given module basis for assignment.

The components with respect to other bases are deleted, in order to avoid any inconsistency. To keep them, use the method add_comp() instead.

INPUT:

• basis – (default: None) basis in which the components are defined; if none is provided, the components are assumed to refer to the module’s default basis

OUTPUT:

• components in the given basis, as an instance of the class Components; if such components did not exist previously, they are created.

EXAMPLES:

Setting components of a type-$$(1,1)$$ tensor:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e')
sage: t = M.tensor((1,1), name='t')
sage: t.set_comp()[0,1] = -3
sage: t.display()
t = -3 e_0⊗e^1
sage: t.set_comp()[1,2] = 2
sage: t.display()
t = -3 e_0⊗e^1 + 2 e_1⊗e^2
sage: t.set_comp(e)
2-indices components w.r.t. Basis (e_0,e_1,e_2) on the
Rank-3 free module M over the Integer Ring

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> e = M.basis('e')
>>> t = M.tensor((Integer(1),Integer(1)), name='t')
>>> t.set_comp()[Integer(0),Integer(1)] = -Integer(3)
>>> t.display()
t = -3 e_0⊗e^1
>>> t.set_comp()[Integer(1),Integer(2)] = Integer(2)
>>> t.display()
t = -3 e_0⊗e^1 + 2 e_1⊗e^2
>>> t.set_comp(e)
2-indices components w.r.t. Basis (e_0,e_1,e_2) on the
Rank-3 free module M over the Integer Ring


Setting components in a new basis:

sage: f =  M.basis('f')
sage: t.set_comp(f)[0,1] = 4
sage: list(t._components) # the components w.r.t. basis e have been deleted
[Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]
sage: t.display(f)
t = 4 f_0⊗f^1

>>> from sage.all import *
>>> f =  M.basis('f')
>>> t.set_comp(f)[Integer(0),Integer(1)] = Integer(4)
>>> list(t._components) # the components w.r.t. basis e have been deleted
[Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]
>>> t.display(f)
t = 4 f_0⊗f^1


The components w.r.t. basis e can be deduced from those w.r.t. basis f, once a relation between the two bases has been set:

sage: a = M.automorphism()
sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]]
sage: M.set_change_of_basis(e, f, a)
sage: t.display(e)
t = -4 e_1⊗e^2
sage: sorted(t._components, key=repr)
[Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring,
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]

>>> from sage.all import *
>>> a = M.automorphism()
>>> a[:] = [[Integer(0),Integer(0),Integer(1)], [Integer(1),Integer(0),Integer(0)], [Integer(0),-Integer(1),Integer(0)]]
>>> M.set_change_of_basis(e, f, a)
>>> t.display(e)
t = -4 e_1⊗e^2
>>> sorted(t._components, key=repr)
[Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring,
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]


Since zero is an immutable element, its components cannot be changed:

sage: z = M.tensor_module(1, 1).zero()
sage: z.set_comp(e)[0,1] = 1
Traceback (most recent call last):
...
ValueError: the components of an immutable element cannot be changed

>>> from sage.all import *
>>> z = M.tensor_module(Integer(1), Integer(1)).zero()
>>> z.set_comp(e)[Integer(0),Integer(1)] = Integer(1)
Traceback (most recent call last):
...
ValueError: the components of an immutable element cannot be changed

set_name(name=None, latex_name=None)[source]#

Set (or change) the text name and LaTeX name of self.

INPUT:

• name – (default: None) string; name given to the tensor

• latex_name – (default: None) string; LaTeX symbol to denote the tensor; if None while name is provided, the LaTeX symbol is set to name

EXAMPLES:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: t = M.tensor((2,1)) ; t
Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring
sage: t.set_name('t') ; t
Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring
sage: latex(t)
t
sage: t.set_name(latex_name=r'\tau') ; t
Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring
sage: latex(t)
\tau

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> t = M.tensor((Integer(2),Integer(1))) ; t
Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring
>>> t.set_name('t') ; t
Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring
>>> latex(t)
t
>>> t.set_name(latex_name=r'\tau') ; t
Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring
>>> latex(t)
\tau

symmetries()[source]#

Print the list of symmetries and antisymmetries of self.

EXAMPLES:

Various symmetries / antisymmetries for a rank-4 tensor:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: t = M.tensor((4,0), name='T') # no symmetry declared
sage: t.symmetries()
no symmetry;  no antisymmetry
sage: t = M.tensor((4,0), name='T', sym=(0,1))
sage: t.symmetries()
symmetry: (0, 1);  no antisymmetry
sage: t = M.tensor((4,0), name='T', sym=[(0,1), (2,3)])
sage: t.symmetries()
symmetries: [(0, 1), (2, 3)];  no antisymmetry
sage: t = M.tensor((4,0), name='T', sym=(0,1), antisym=(2,3))
sage: t.symmetries()
symmetry: (0, 1);  antisymmetry: (2, 3)

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> t = M.tensor((Integer(4),Integer(0)), name='T') # no symmetry declared
>>> t.symmetries()
no symmetry;  no antisymmetry
>>> t = M.tensor((Integer(4),Integer(0)), name='T', sym=(Integer(0),Integer(1)))
>>> t.symmetries()
symmetry: (0, 1);  no antisymmetry
>>> t = M.tensor((Integer(4),Integer(0)), name='T', sym=[(Integer(0),Integer(1)), (Integer(2),Integer(3))])
>>> t.symmetries()
symmetries: [(0, 1), (2, 3)];  no antisymmetry
>>> t = M.tensor((Integer(4),Integer(0)), name='T', sym=(Integer(0),Integer(1)), antisym=(Integer(2),Integer(3)))
>>> t.symmetries()
symmetry: (0, 1);  antisymmetry: (2, 3)

symmetrize(*pos, **kwargs)[source]#

Symmetrization over some arguments.

INPUT:

• pos – list of argument positions involved in the symmetrization (with the convention position=0 for the first argument); if none, the symmetrization is performed over all the arguments

• basis – (default: None) module basis with respect to which the component computation is to be performed; if none, the module’s default basis is used if the tensor field has already components in it; otherwise another basis w.r.t. which the tensor has components will be picked

OUTPUT:

EXAMPLES:

Symmetrization of a tensor of type $$(2,0)$$:

sage: M = FiniteRankFreeModule(QQ, 3, name='M')
sage: e = M.basis('e')
sage: t = M.tensor((2,0))
sage: t[:] = [[2,1,-3],[0,-4,5],[-1,4,2]]
sage: s = t.symmetrize() ; s
Type-(2,0) tensor on the 3-dimensional vector space M over the
Rational Field
sage: t[:], s[:]
(
[ 2  1 -3]  [  2 1/2  -2]
[ 0 -4  5]  [1/2  -4 9/2]
[-1  4  2], [ -2 9/2   2]
)
sage: s.symmetries()
symmetry: (0, 1);  no antisymmetry
sage: all(s[i,j] == 1/2*(t[i,j]+t[j,i])   # check:
....:     for i in range(3) for j in range(3))
True

>>> from sage.all import *
>>> M = FiniteRankFreeModule(QQ, Integer(3), name='M')
>>> e = M.basis('e')
>>> t = M.tensor((Integer(2),Integer(0)))
>>> t[:] = [[Integer(2),Integer(1),-Integer(3)],[Integer(0),-Integer(4),Integer(5)],[-Integer(1),Integer(4),Integer(2)]]
>>> s = t.symmetrize() ; s
Type-(2,0) tensor on the 3-dimensional vector space M over the
Rational Field
>>> t[:], s[:]
(
[ 2  1 -3]  [  2 1/2  -2]
[ 0 -4  5]  [1/2  -4 9/2]
[-1  4  2], [ -2 9/2   2]
)
>>> s.symmetries()
symmetry: (0, 1);  no antisymmetry
>>> all(s[i,j] == Integer(1)/Integer(2)*(t[i,j]+t[j,i])   # check:
...     for i in range(Integer(3)) for j in range(Integer(3)))
True


Instead of invoking the method symmetrize(), one may use the index notation with parentheses to denote the symmetrization; it suffices to pass the indices as a string inside square brackets:

sage: t['(ij)']
Type-(2,0) tensor on the 3-dimensional vector space M over the
Rational Field
sage: t['(ij)'].symmetries()
symmetry: (0, 1);  no antisymmetry
sage: t['(ij)'] == t.symmetrize()
True

>>> from sage.all import *
>>> t['(ij)']
Type-(2,0) tensor on the 3-dimensional vector space M over the
Rational Field
>>> t['(ij)'].symmetries()
symmetry: (0, 1);  no antisymmetry
>>> t['(ij)'] == t.symmetrize()
True


The indices names are not significant; they can even be replaced by dots:

sage: t['(..)'] == t.symmetrize()
True

>>> from sage.all import *
>>> t['(..)'] == t.symmetrize()
True


The LaTeX notation can be used as well:

sage: t['^{(ij)}'] == t.symmetrize()
True

>>> from sage.all import *
>>> t['^{(ij)}'] == t.symmetrize()
True


Symmetrization of a tensor of type $$(0,3)$$ on the first two arguments:

sage: t = M.tensor((0,3))
sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]],
....:         [[10,-11,12], [13,14,-15], [16,17,18]],
....:         [[19,-20,-21], [-22,23,24], [25,26,-27]]]
sage: s = t.symmetrize(0,1) ; s  # (0,1) = the first two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
symmetry: (0, 1);  no antisymmetry
sage: s[:]
[[[1, 2, 3], [3, -3, 9], [13, -6, -15]],
[[3, -3, 9], [13, 14, -15], [-3, 20, 21]],
[[13, -6, -15], [-3, 20, 21], [25, 26, -27]]]
sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[j,i,k])   # Check:
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.symmetrize(0,1) == s  # another test
True

>>> from sage.all import *
>>> t = M.tensor((Integer(0),Integer(3)))
>>> t[:] = [[[Integer(1),Integer(2),Integer(3)], [-Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),-Integer(9)]],
...         [[Integer(10),-Integer(11),Integer(12)], [Integer(13),Integer(14),-Integer(15)], [Integer(16),Integer(17),Integer(18)]],
...         [[Integer(19),-Integer(20),-Integer(21)], [-Integer(22),Integer(23),Integer(24)], [Integer(25),Integer(26),-Integer(27)]]]
>>> s = t.symmetrize(Integer(0),Integer(1)) ; s  # (0,1) = the first two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
symmetry: (0, 1);  no antisymmetry
>>> s[:]
[[[1, 2, 3], [3, -3, 9], [13, -6, -15]],
[[3, -3, 9], [13, 14, -15], [-3, 20, 21]],
[[13, -6, -15], [-3, 20, 21], [25, 26, -27]]]
>>> all(s[i,j,k] == Integer(1)/Integer(2)*(t[i,j,k]+t[j,i,k])   # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.symmetrize(Integer(0),Integer(1)) == s  # another test
True


Again the index notation can be used:

sage: t['_(ij)k'] == t.symmetrize(0,1)
True
sage: t['_(..).'] == t.symmetrize(0,1)  # no index name
True
sage: t['_{(ij)k}'] == t.symmetrize(0,1)  # LaTeX notation
True
sage: t['_{(..).}'] == t.symmetrize(0,1)  # this also allowed
True

>>> from sage.all import *
>>> t['_(ij)k'] == t.symmetrize(Integer(0),Integer(1))
True
>>> t['_(..).'] == t.symmetrize(Integer(0),Integer(1))  # no index name
True
>>> t['_{(ij)k}'] == t.symmetrize(Integer(0),Integer(1))  # LaTeX notation
True
>>> t['_{(..).}'] == t.symmetrize(Integer(0),Integer(1))  # this also allowed
True


Symmetrization of a tensor of type $$(0,3)$$ on the first and last arguments:

sage: s = t.symmetrize(0,2) ; s  # (0,2) = first and last arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
symmetry: (0, 2);  no antisymmetry
sage: s[:]
[[[1, 6, 11], [-4, 9, -8], [7, 12, 8]],
[[6, -11, -4], [9, 14, 4], [12, 17, 22]],
[[11, -4, -21], [-8, 4, 24], [8, 22, -27]]]
sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[k,j,i])
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.symmetrize(0,2) == s  # another test
True

>>> from sage.all import *
>>> s = t.symmetrize(Integer(0),Integer(2)) ; s  # (0,2) = first and last arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
symmetry: (0, 2);  no antisymmetry
>>> s[:]
[[[1, 6, 11], [-4, 9, -8], [7, 12, 8]],
[[6, -11, -4], [9, 14, 4], [12, 17, 22]],
[[11, -4, -21], [-8, 4, 24], [8, 22, -27]]]
>>> all(s[i,j,k] == Integer(1)/Integer(2)*(t[i,j,k]+t[k,j,i])
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.symmetrize(Integer(0),Integer(2)) == s  # another test
True


Symmetrization of a tensor of type $$(0,3)$$ on the last two arguments:

sage: s = t.symmetrize(1,2) ; s  # (1,2) = the last two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
symmetry: (1, 2);  no antisymmetry
sage: s[:]
[[[1, -1, 5], [-1, 5, 7], [5, 7, -9]],
[[10, 1, 14], [1, 14, 1], [14, 1, 18]],
[[19, -21, 2], [-21, 23, 25], [2, 25, -27]]]
sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[i,k,j])   # Check:
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.symmetrize(1,2) == s  # another test
True

>>> from sage.all import *
>>> s = t.symmetrize(Integer(1),Integer(2)) ; s  # (1,2) = the last two arguments
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
symmetry: (1, 2);  no antisymmetry
>>> s[:]
[[[1, -1, 5], [-1, 5, 7], [5, 7, -9]],
[[10, 1, 14], [1, 14, 1], [14, 1, 18]],
[[19, -21, 2], [-21, 23, 25], [2, 25, -27]]]
>>> all(s[i,j,k] == Integer(1)/Integer(2)*(t[i,j,k]+t[i,k,j])   # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.symmetrize(Integer(1),Integer(2)) == s  # another test
True


Use of the index notation:

sage: t['_i(jk)'] == t.symmetrize(1,2)
True
sage: t['_.(..)'] == t.symmetrize(1,2)
True
sage: t['_{i(jk)}'] == t.symmetrize(1,2)  # LaTeX notation
True

>>> from sage.all import *
>>> t['_i(jk)'] == t.symmetrize(Integer(1),Integer(2))
True
>>> t['_.(..)'] == t.symmetrize(Integer(1),Integer(2))
True
>>> t['_{i(jk)}'] == t.symmetrize(Integer(1),Integer(2))  # LaTeX notation
True


Full symmetrization of a tensor of type $$(0,3)$$:

sage: s = t.symmetrize() ; s
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
sage: s.symmetries()
symmetry: (0, 1, 2);  no antisymmetry
sage: s[:]
[[[1, 8/3, 29/3], [8/3, 7/3, 0], [29/3, 0, -5/3]],
[[8/3, 7/3, 0], [7/3, 14, 25/3], [0, 25/3, 68/3]],
[[29/3, 0, -5/3], [0, 25/3, 68/3], [-5/3, 68/3, -27]]]
sage: all(s[i,j,k] == 1/6*(t[i,j,k]+t[i,k,j]+t[j,k,i]+t[j,i,k]+t[k,i,j]+t[k,j,i])  # Check:
....:     for i in range(3) for j in range(3) for k in range(3))
True
sage: s.symmetrize() == s  # another test
True

>>> from sage.all import *
>>> s = t.symmetrize() ; s
Type-(0,3) tensor on the 3-dimensional vector space M over the
Rational Field
>>> s.symmetries()
symmetry: (0, 1, 2);  no antisymmetry
>>> s[:]
[[[1, 8/3, 29/3], [8/3, 7/3, 0], [29/3, 0, -5/3]],
[[8/3, 7/3, 0], [7/3, 14, 25/3], [0, 25/3, 68/3]],
[[29/3, 0, -5/3], [0, 25/3, 68/3], [-5/3, 68/3, -27]]]
>>> all(s[i,j,k] == Integer(1)/Integer(6)*(t[i,j,k]+t[i,k,j]+t[j,k,i]+t[j,i,k]+t[k,i,j]+t[k,j,i])  # Check:
...     for i in range(Integer(3)) for j in range(Integer(3)) for k in range(Integer(3)))
True
>>> s.symmetrize() == s  # another test
True


Index notation for the full symmetrization:

sage: t['_(ijk)'] == t.symmetrize()
True
sage: t['_{(ijk)}'] == t.symmetrize()  # LaTeX notation
True

>>> from sage.all import *
>>> t['_(ijk)'] == t.symmetrize()
True
>>> t['_{(ijk)}'] == t.symmetrize()  # LaTeX notation
True


Symmetrization can be performed only on arguments on the same type:

sage: t = M.tensor((1,2))
sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]],
....:         [[10,-11,12], [13,14,-15], [16,17,18]],
....:         [[19,-20,-21], [-22,23,24], [25,26,-27]]]
sage: s = t.symmetrize(0,1)
Traceback (most recent call last):
...
TypeError: 0 is a contravariant position, while 1 is a covariant position;
symmetrization is meaningful only on tensor arguments of the same type
sage: s = t.symmetrize(1,2) # OK: both 1 and 2 are covariant positions

>>> from sage.all import *
>>> t = M.tensor((Integer(1),Integer(2)))
>>> t[:] = [[[Integer(1),Integer(2),Integer(3)], [-Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),-Integer(9)]],
...         [[Integer(10),-Integer(11),Integer(12)], [Integer(13),Integer(14),-Integer(15)], [Integer(16),Integer(17),Integer(18)]],
...         [[Integer(19),-Integer(20),-Integer(21)], [-Integer(22),Integer(23),Integer(24)], [Integer(25),Integer(26),-Integer(27)]]]
>>> s = t.symmetrize(Integer(0),Integer(1))
Traceback (most recent call last):
...
TypeError: 0 is a contravariant position, while 1 is a covariant position;
symmetrization is meaningful only on tensor arguments of the same type
>>> s = t.symmetrize(Integer(1),Integer(2)) # OK: both 1 and 2 are covariant positions


The order of positions does not matter:

sage: t.symmetrize(2,1) == t.symmetrize(1,2)
True

>>> from sage.all import *
>>> t.symmetrize(Integer(2),Integer(1)) == t.symmetrize(Integer(1),Integer(2))
True


Use of the index notation:

sage: t['^i_(jk)'] == t.symmetrize(1,2)
True
sage: t['^._(..)'] ==  t.symmetrize(1,2)
True

>>> from sage.all import *
>>> t['^i_(jk)'] == t.symmetrize(Integer(1),Integer(2))
True
>>> t['^._(..)'] ==  t.symmetrize(Integer(1),Integer(2))
True


The character ^ can be skipped, the character _ being sufficient to separate contravariant indices from covariant ones:

sage: t['i_(jk)'] == t.symmetrize(1,2)
True

>>> from sage.all import *
>>> t['i_(jk)'] == t.symmetrize(Integer(1),Integer(2))
True


The LaTeX notation can be employed:

sage: t['^{i}_{(jk)}'] == t.symmetrize(1,2)
True

>>> from sage.all import *
>>> t['^{i}_{(jk)}'] == t.symmetrize(Integer(1),Integer(2))
True

tensor_rank()[source]#

Return the tensor rank of self.

OUTPUT:

• integer k+l, where k is the contravariant rank and l is the covariant rank

EXAMPLES:

sage: M = FiniteRankFreeModule(ZZ, 3)
sage: M.an_element().tensor_rank()
1
sage: t = M.tensor((2,1))
sage: t.tensor_rank()
3

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3))
>>> M.an_element().tensor_rank()
1
>>> t = M.tensor((Integer(2),Integer(1)))
>>> t.tensor_rank()
3

tensor_type()[source]#

Return the tensor type of self.

OUTPUT:

• pair (k, l), where k is the contravariant rank and l is the covariant rank

EXAMPLES:

sage: M = FiniteRankFreeModule(ZZ, 3)
sage: M.an_element().tensor_type()
(1, 0)
sage: t = M.tensor((2,1))
sage: t.tensor_type()
(2, 1)

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3))
>>> M.an_element().tensor_type()
(1, 0)
>>> t = M.tensor((Integer(2),Integer(1)))
>>> t.tensor_type()
(2, 1)

trace(pos1=0, pos2=1, using=None)[source]#

Trace (contraction) on two slots of the tensor.

If a non-degenerate form is provided, the trace of a type-$$(0,2)$$ tensor is computed by first raising the last index.

INPUT:

• pos1 – (default: 0) position of the first index for the contraction, with the convention pos1=0 for the first slot

• pos2 – (default: 1) position of the second index for the contraction, with the same convention as for pos1; the variance type of pos2 must be opposite to that of pos1

• using – (default: None) a non-degenerate form

OUTPUT:

• tensor or scalar resulting from the (pos1, pos2) contraction

EXAMPLES:

Trace of a type-$$(1,1)$$ tensor:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e') ; e
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
sage: a = M.tensor((1,1), name='a') ; a
Type-(1,1) tensor a on the Rank-3 free module M over the Integer Ring
sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]]
sage: a.trace()
15
sage: a.trace(0,1)  # equivalent to above (contraction of slot 0 with slot 1)
15
sage: a.trace(1,0)  # the order of the slots does not matter
15

>>> from sage.all import *
>>> M = FiniteRankFreeModule(ZZ, Integer(3), name='M')
>>> e = M.basis('e') ; e
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
>>> a = M.tensor((Integer(1),Integer(1)), name='a') ; a
Type-(1,1) tensor a on the Rank-3 free module M over the Integer Ring
>>> a[:] = [[Integer(1),Integer(2),Integer(3)], [Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),Integer(9)]]
>>> a.trace()
15
>>> a.trace(Integer(0),Integer(1))  # equivalent to above (contraction of slot 0 with slot 1)
15
>>> a.trace(Integer(1),Integer(0))  # the order of the slots does not matter
15


Instead of the explicit call to the method trace(), one may use the index notation with Einstein convention (summation over repeated indices); it suffices to pass the indices as a string inside square brackets:

sage: a['^i_i']
15

>>> from sage.all import *
>>> a['^i_i']
15


The letter ‘i’ to denote the repeated index can be replaced by any other letter:

sage: a['^s_s']
15

>>> from sage.all import *
>>> a['^s_s']
15


Moreover, the symbol ^ can be omitted:

sage: a['i_i']
15

>>> from sage.all import *
>>> a['i_i']
15


The contraction on two slots having the same tensor type cannot occur:

sage: b = M.tensor((2,0), name='b') ; b
Type-(2,0) tensor b on the Rank-3 free module M over the Integer Ring
sage: b[:] = [[1,2,3], [4,5,6], [7,8,9]]
sage: b.trace(0,1)
Traceback (most recent call last):
...
IndexError: contraction on two contravariant indices is not allowed

>>> from sage.all import *
>>> b = M.tensor((Integer(2),Integer(0)), name='b') ; b
Type-(2,0) tensor b on the Rank-3 free module M over the Integer Ring
>>> b[:] = [[Integer(1),Integer(2),Integer(3)], [Integer(4),Integer(5),Integer(6)], [Integer(7),Integer(8),Integer(9)]]
>>> b.trace(Integer(0),Integer(1))
Traceback (most recent call last):
...
IndexError: contraction on two contravariant indices is not allowed


The contraction either preserves or destroys the symmetries:

sage: b = M.alternating_form(2, 'b') ; b
Alternating form b of degree 2 on the Rank-3 free module M
over the Integer Ring
sage: b[0,1], b[0,2], b[1,2] = 3, 2, 1
sage: t = a*b ; t
Type-(1,3) tensor a⊗b on the Rank-3 free module M
over the Integer Ring

>>> from sage.all import *
>>> b = M.alternating_form(Integer(2), 'b') ; b
Alternating form b of degree 2 on the Rank-3 free module M
over the Integer Ring
>>> b[Integer(0),Integer(1)], b[Integer(0),Integer(2)], b[Integer(1),Integer(2)] = Integer(3), Integer(2), Integer(1)
>>> t = a*b ; t
Type-(1,3) tensor a⊗b on the Rank-3 free module M
over the Integer Ring


By construction, t is a tensor field antisymmetric w.r.t. its last two slots:

sage: t.symmetries()
no symmetry;  antisymmetry: (2, 3)
sage: s = t.trace(0,1) ; s   # contraction on the first two slots
Alternating form of degree 2 on the
Rank-3 free module M over the Integer Ring
sage: s.symmetries()    # the antisymmetry is preserved
no symmetry;  antisymmetry: (0, 1)
sage: s[:]
[  0  45  30]
[-45   0  15]
[-30 -15   0]
sage: s == 15*b  # check
True
sage: s = t.trace(0,2) ; s   # contraction on the first and third slots
Type-(0,2) tensor on the Rank-3 free module M over the Integer Ring
sage: s.symmetries()  # the antisymmetry has been destroyed by the above contraction:
no symmetry;  no antisymmetry
sage: s[:]  # indeed:
[-26  -4   6]
[-31  -2   9]
[-36   0  12]
sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange())
....:          for j in M.irange()] for i in M.irange()] )  # check
True

>>> from sage.all import *
>>> t.symmetries()
no symmetry;  antisymmetry: (2, 3)
>>> s = t.trace(Integer(0),Integer(1)) ; s   # contraction on the first two slots
Alternating form of degree 2 on the
Rank-3 free module M over the Integer Ring
>>> s.symmetries()    # the antisymmetry is preserved
no symmetry;  antisymmetry: (0, 1)
>>> s[:]
[  0  45  30]
[-45   0  15]
[-30 -15   0]
>>> s == Integer(15)*b  # check
True
>>> s = t.trace(Integer(0),Integer(2)) ; s   # contraction on the first and third slots
Type-(0,2) tensor on the Rank-3 free module M over the Integer Ring
>>> s.symmetries()  # the antisymmetry has been destroyed by the above contraction:
no symmetry;  no antisymmetry
>>> s[:]  # indeed:
[-26  -4   6]
[-31  -2   9]
[-36   0  12]
>>> s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange())
...          for j in M.irange()] for i in M.irange()] )  # check
True


Use of index notation instead of trace():

sage: t['^k_kij'] == t.trace(0,1)
True
sage: t['^k_{kij}'] == t.trace(0,1) # LaTeX notation
True
sage: t['^k_ikj'] == t.trace(0,2)
True
sage: t['^k_ijk'] == t.trace(0,3)
True

>>> from sage.all import *
>>> t['^k_kij'] == t.trace(Integer(0),Integer(1))
True
>>> t['^k_{kij}'] == t.trace(Integer(0),Integer(1)) # LaTeX notation
True
>>> t['^k_ikj'] == t.trace(Integer(0),Integer(2))
True
>>> t['^k_ijk'] == t.trace(Integer(0),Integer(3))
True


Index symbols not involved in the contraction may be replaced by dots:

sage: t['^k_k..'] == t.trace(0,1)
True
sage: t['^k_.k.'] == t.trace(0,2)
True
sage: t['^k_..k'] == t.trace(0,3)
True

>>> from sage.all import *
>>> t['^k_k..'] == t.trace(Integer(0),Integer(1))
True
>>> t['^k_.k.'] == t.trace(Integer(0),Integer(2))
True
>>> t['^k_..k'] == t.trace(Integer(0),Integer(3))
True