Tensors on free modules - Tensors on free modules of finite rank

Skip to content
Back to top
View this page
Edit this page

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

- `TensorFreeModule` for `FreeModuleTensor`;

- `FiniteRankFreeModule` for `FiniteRankFreeModuleElement`;

- `ExtPowerFreeModule` for `AlternatingContrTensor`;

- `ExtPowerDualFreeModule` for `FreeModuleAltForm`;

- `FreeModuleLinearGroup` for `FreeModuleAutomorphism`.

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

    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

Python

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

    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

[Python]

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

    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

Python

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

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

[Python]

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

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

Python

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

    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

[Python]

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

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

Python

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

    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

[Python]

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

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

Python

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

Only nonzero components are actually stored, in the dictionary `_comp` of class `Components`, whose keys are the indices:

Sage

    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

[Python]

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

    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

Python

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

    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

[Python]

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

  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

      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

  Python

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

      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

  [Python]

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

  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

        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

    Python

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

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

    [Python]

        >>> from sage.all import *
        >>> f =  M.basis('f')
        >>> t.add_comp(f)[Integer(0),Integer(1)] = Integer(4)

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

    Sage

        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

    Python

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

        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

    [Python]

        >>> from sage.all import *
        >>> z = M.tensor_module(Integer(1), Integer(1)).zero()
        >>> z.add_comp(e)[Integer(0),Integer(1)] = Integer(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:

    - the antisymmetrized tensor (instance of `FreeModuleTensor`)

    EXAMPLES:

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

    Sage

        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

    Python

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

        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

    [Python]

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

        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

    Python

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

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

    [Python]

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

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

    Python

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

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

    Sage

        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

    [Python]

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

        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

    Python

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

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

    [Python]

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

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

    Sage

        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

    Python

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

        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

    [Python]

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

        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

    Python

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

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

    [Python]

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

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

    Python

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

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

    [Python]

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

  base_module()

    Return the module on which `self` is defined.

    OUTPUT:

    - instance of `FiniteRankFreeModule` representing the free module on which the tensor is defined.

    EXAMPLES:

    Sage

        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

    Python

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

    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:

    - `other` – a tensor (instance of `FreeModuleTensor`)

    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

        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)

    Python

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

        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]

    [Python]

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

        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

    Python

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

        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]

    [Python]

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

    alias of `components()`.

  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

        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]

    Python

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

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

    [Python]

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

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

    Python

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

        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]

    [Python]

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

    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

        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

    Python

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

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

    [Python]

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

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

    Python

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

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

    [Python]

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

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

    Python

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

    We have also:

    Sage

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

    [Python]

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

        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

    Python

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

        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

    [Python]

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

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

    Python

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

    The index `i` can be replaced by a dot:

    Sage

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

    [Python]

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

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

    Python

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

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

    [Python]

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

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

    Python

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

        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

    [Python]

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

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

    Python

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

    LaTeX notation are allowed:

    Sage

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

    [Python]

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

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

    Python

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

        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

    [Python]

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

        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

    Python

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

    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

        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

    Python

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

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

    [Python]

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

    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

        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

    Python

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

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

    [Python]

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

    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

        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]

    Python

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

        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]

    [Python]

        >>> from sage.all import *
        >>> u.add_comp(e)[:] = [Integer(2),Integer(1),-Integer(5)]
        >>> 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)

    alias of `display()`.

  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

        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}

    Python

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

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

    [Python]

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

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

    Sage

        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}

    Python

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

        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}

    [Python]

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

        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

    Python

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

        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

    [Python]

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

        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}

    Python

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

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

    [Python]

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

        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

    Python

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

    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` – boolean (default: `True`); if `True`, only nonzero components are displayed

    - `only_nonredundant` – boolean (default: `False`); 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

        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

    Python

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

        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}

    [Python]

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

        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

    Python

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

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

    [Python]

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

        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

    Python

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

        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

    [Python]

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

    Return a basis in which the tensor components are defined.

    The free module’s default basis is privileged.

    OUTPUT:

    - instance of `FreeModuleBasis` representing the basis

    EXAMPLES:

    Sage

        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

    Python

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

    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

        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

    Python

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

        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

    [Python]

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

        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]

    Python

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

        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

    [Python]

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

    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

        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

    Python

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

    Print the list of symmetries and antisymmetries of `self`.

    EXAMPLES:

    Various symmetries / antisymmetries for a rank-4 tensor:

    Sage

        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)

    Python

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

    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:

    - the symmetrized tensor (instance of `FreeModuleTensor`)

    EXAMPLES:

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

    Sage

        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

    Python

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

        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

    [Python]

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

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

    Python

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

    The LaTeX notation can be used as well:

    Sage

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

    [Python]

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

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

    Sage

        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

    Python

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

        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

    [Python]

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

        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

    Python

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

        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

    [Python]

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

        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

    Python

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

        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

    [Python]

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

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

    Python

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

        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

    [Python]

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

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

    Python

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

    Use of the index notation:

    Sage

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

    [Python]

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

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

    Python

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

    The LaTeX notation can be employed:

    Sage

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

    [Python]

        >>> from sage.all import *
        >>> t['^{i}_{(jk)}'] == t.symmetrize(Integer(1),Integer(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

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

    Python

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

    Return the tensor type of `self`.

    OUTPUT:

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

    EXAMPLES:

    Sage

        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)

    Python

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

    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

        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

    Python

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

        sage: a['^i_i']
        15

    [Python]

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

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

    Sage

        sage: a['^s_s']
        15

    Python

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

    Moreover, the symbol `^` can be omitted:

    Sage

        sage: a['i_i']
        15

    [Python]

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

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

    Sage

        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

    Python

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

        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

    [Python]

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

        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

    Python

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

        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

    [Python]

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

        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

    Python

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