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:

  • AlternatingContrTensor for fully antisymmetric type-\((k, 0)\) tensors (alternating contravariant tensors);

    • FiniteRankFreeModuleElement for elements of \(M\), considered as type-\((1,0)\) tensors thanks to the canonical identification \(M^{**}=M\) (which holds since \(M\) is a free module of finite rank);

  • FreeModuleAltForm for fully antisymmetric type-\((0, l)\) tensors (alternating forms);

  • FreeModuleAutomorphism for type-\((1,1)\) tensors representing invertible endomorphisms.

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

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

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

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]

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]

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

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]

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

The components are managed by the class Components:

sage: 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

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

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

Bases: ModuleElementWithMutability

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

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
add_comp(basis=None)#

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.add_comp()[0,1] = -3
sage: t.display()
t = -3 e_0⊗e^1
sage: t.add_comp()[1,2] = 2
sage: t.display()
t = -3 e_0⊗e^1 + 2 e_1⊗e^2
sage: t.add_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

Adding components in a new basis:

sage: f =  M.basis('f')
sage: t.add_comp(f)[0,1] = 4

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

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

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

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

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

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

The LaTeX notation is recognized:

sage: 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

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

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

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

sage: t['_i[jk]'] == t.antisymmetrize(1,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

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

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

The order of positions does not matter:

sage: t.antisymmetrize(2,1) == t.antisymmetrize(1,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

The character ‘^’ can be skipped:

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

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

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:

  • instance of FreeModuleBasis representing the common basis; if no common basis is found, None is returned

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)

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]

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

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]
comp(basis=None, from_basis=None)#

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:

  • components in the basis basis, as an instance of the class Components

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]

A shortcut is t.comp():

sage: 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]

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]
components(basis=None, from_basis=None)#

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:

  • components in the basis basis, as an instance of the class Components

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]

A shortcut is t.comp():

sage: 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]

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]
contract(*args)#

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

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

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

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

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

We have also:

sage: 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

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

Using the index notation with Einstein convention:

sage: 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

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

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

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]]]

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

Using index notation:

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

LaTeX notation are allowed:

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

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

sage: a['.l_.']*b['_.l'] == a.contract(1, b, 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

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
copy(name=None, latex_name=None)#

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

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

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

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
del_other_comp(basis=None)#

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: u.add_comp(f)[:] = [0,4,2]
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]

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]
disp(basis=None, format_spec=None)#

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}

A shortcut is disp():

sage: 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}

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}

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

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

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}

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

Check that the bug reported in github 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
display(basis=None, format_spec=None)#

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}

A shortcut is disp():

sage: 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}

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}

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

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

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}

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

Check that the bug reported in github 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
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)#

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

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}

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

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

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

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

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
set_comp(basis=None)#

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

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

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]

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
set_name(name=None, latex_name=None)#

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

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)
symmetrize(*pos, **kwargs)#

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

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

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

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

The LaTeX notation can be used as well:

sage: 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

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

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

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

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

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

Index notation for the full symmetrization:

sage: t['_(ijk)'] == t.symmetrize()
True
sage: 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

The order of positions does not matter:

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

Use of the index notation:

sage: t['^i_(jk)'] == t.symmetrize(1,2)
True
sage: t['^._(..)'] ==  t.symmetrize(1,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

The LaTeX notation can be employed:

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

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

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)
trace(pos1=0, pos2=1, using=None)#

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

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

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

sage: a['^s_s']
15

Moreover, the symbol ^ can be omitted:

sage: 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

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

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

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

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