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:
where \(R\) is the commutative ring over which the free module \(M\) is defined
and \(M^* = \mathrm{Hom}_R(M,R)\) is the dual of \(M\). The integer \(k + l\) is
called the tensor rank. The set \(T^{(k,l)}(M)\) of tensors of type \((k,l)\)
on \(M\) is a free module of finite rank over \(R\), described by the
class TensorFreeModule
.
Various derived classes of FreeModuleTensor
are devoted to specific
tensors:
AlternatingContrTensor
for fully antisymmetric type-\((k, 0)\) tensors (alternating contravariant tensors);FiniteRankFreeModuleElement
for elements of \(M\), considered as type-\((1,0)\) tensors thanks to the canonical identification \(M^{**}=M\) (which holds since \(M\) is a free module of finite rank);
FreeModuleAltForm
for fully antisymmetric type-\((0, l)\) tensors (alternating forms);FreeModuleAutomorphism
for type-\((1,1)\) tensors representing invertible endomorphisms.
Each of these classes is a Sage element class, the corresponding parent class being:
AUTHORS:
Eric Gourgoulhon, Michal Bejger (2014-2015): initial version
Michael Jung (2019): improve treatment of the zero element; add method
copy_from
REFERENCES:
Chap. 21 of R. Godement : Algebra [God1968]
Chap. 12 of J. M. Lee: Introduction to Smooth Manifolds [Lee2013] (only when the free module is a vector space)
Chap. 2 of B. O’Neill: Semi-Riemannian Geometry [ONe1983]
EXAMPLES:
A tensor of type \((1, 1)\) on a rank-3 free module over \(\ZZ\):
sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: t = M.tensor((1,1), name='t') ; t
Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring
sage: t.parent()
Free module of type-(1,1) tensors on the Rank-3 free module M
over the Integer Ring
sage: t.parent() is M.tensor_module(1,1)
True
sage: t in M.tensor_module(1,1)
True
Setting some component of the tensor in a given basis:
sage: e = M.basis('e') ; e
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
sage: t.set_comp(e)[0,0] = -3 # the component [0,0] w.r.t. basis e is set to -3
The unset components are assumed to be zero:
sage: t.comp(e)[:] # list of all components w.r.t. basis e
[-3 0 0]
[ 0 0 0]
[ 0 0 0]
sage: t.display(e) # displays the expansion of t on the basis e_i⊗e^j of T^(1,1)(M)
t = -3 e_0⊗e^0
The commands t.set_comp(e)
and t.comp(e)
can be abridged by providing
the basis as the first argument in the square brackets:
sage: t[e,0,0] = -3
sage: t[e,:]
[-3 0 0]
[ 0 0 0]
[ 0 0 0]
Actually, since e
is M
’s default basis, the mention of e
can be omitted:
sage: t[0,0] = -3
sage: t[:]
[-3 0 0]
[ 0 0 0]
[ 0 0 0]
For tensors of rank 2, the matrix of components w.r.t. a given basis is
obtained via the function matrix
:
sage: matrix(t.comp(e))
[-3 0 0]
[ 0 0 0]
[ 0 0 0]
sage: matrix(t.comp(e)).parent()
Full MatrixSpace of 3 by 3 dense matrices over Integer Ring
Tensor components can be modified (reset) at any time:
sage: t[0,0] = 0
sage: t[:]
[0 0 0]
[0 0 0]
[0 0 0]
Checking that t
is zero:
sage: t.is_zero()
True
sage: t == 0
True
sage: t == M.tensor_module(1,1).zero() # the zero element of the module of all type-(1,1) tensors on M
True
The components are managed by the class
Components
:
sage: type(t.comp(e))
<class 'sage.tensor.modules.comp.Components'>
Only non-zero components are actually stored, in the dictionary _comp
of class Components
, whose keys are
the indices:
sage: t.comp(e)._comp
{}
sage: t.set_comp(e)[0,0] = -3 ; t.set_comp(e)[1,2] = 2
sage: t.comp(e)._comp # random output order (dictionary)
{(0, 0): -3, (1, 2): 2}
sage: t.display(e)
t = -3 e_0⊗e^0 + 2 e_1⊗e^2
Further tests of the comparison operator:
sage: t.is_zero()
False
sage: t == 0
False
sage: t == M.tensor_module(1,1).zero()
False
sage: t1 = t.copy()
sage: t1 == t
True
sage: t1[2,0] = 4
sage: t1 == t
False
As a multilinear map \(M^* \times M \rightarrow \ZZ\), the type-\((1,1)\)
tensor t
acts on pairs formed by a linear form and a module element:
sage: a = M.linear_form(name='a') ; a[:] = (2, 1, -3) ; a
Linear form a on the Rank-3 free module M over the Integer Ring
sage: b = M([1,-6,2], name='b') ; b
Element b of the Rank-3 free module M over the Integer Ring
sage: t(a,b)
-2
- class sage.tensor.modules.free_module_tensor.FreeModuleTensor(fmodule, tensor_type, name=None, latex_name=None, sym=None, antisym=None, parent=None)#
Bases:
sage.structure.element.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 ofFiniteRankFreeModule
tensor_type
– pair(k, l)
withk
being the contravariant rank andl
the covariant rankname
– (default:None
) name given to the tensorlatex_name
– (default:None
) LaTeX symbol to denote the tensor; if none is provided, the LaTeX symbol is set toname
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 conventionposition=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 forsym
parent
– (default:None
) some specific parent (e.g. exterior power for alternating forms); ifNone
,fmodule.tensor_module(k,l)
is used
EXAMPLES:
A tensor of type \((1,1)\) on a rank-3 free module over \(\ZZ\):
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring
Tensors are Element objects whose parents are tensor free modules:
sage: t.parent() Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring sage: t.parent() is M.tensor_module(1,1) True
- add_comp(basis=None)#
Return the components of
self
w.r.t. a given module basis for assignment, keeping the components w.r.t. other bases.To delete the components w.r.t. other bases, use the method
set_comp()
instead.INPUT:
basis
– (default:None
) basis in which the components are defined; if none is provided, the components are assumed to refer to the module’s default basis
Warning
If the tensor has already components in other bases, it is the user’s responsibility to make sure that the components to be added are consistent with them.
OUTPUT:
components in the given basis, as an instance of the class
Components
; if such components did not exist previously, they are created
EXAMPLES:
Setting components of a type-\((1,1)\) tensor:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t.add_comp()[0,1] = -3 sage: t.display() t = -3 e_0⊗e^1 sage: t.add_comp()[1,2] = 2 sage: t.display() t = -3 e_0⊗e^1 + 2 e_1⊗e^2 sage: t.add_comp(e) 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
Adding components in a new basis:
sage: f = M.basis('f') sage: t.add_comp(f)[0,1] = 4
The components w.r.t. basis e have been kept:
sage: sorted(t._components, key=repr) [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] sage: t.display(f) t = 4 f_0⊗f^1 sage: t.display(e) t = -3 e_0⊗e^1 + 2 e_1⊗e^2
Since zero is an immutable element, its components cannot be changed:
sage: z = M.tensor_module(1, 1).zero() sage: z.add_comp(e)[0,1] = 1 Traceback (most recent call last): ... ValueError: the components of an immutable element cannot be changed
- antisymmetrize(*pos, **kwargs)#
Antisymmetrization over some arguments.
INPUT:
pos
– list of argument positions involved in the antisymmetrization (with the conventionposition=0
for the first argument); if none, the antisymmetrization is performed over all the argumentsbasis
– (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: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,0)) sage: t[:] = [[1,-2,3], [4,5,6], [7,8,-9]] sage: s = t.antisymmetrize() ; s Alternating contravariant tensor of degree 2 on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) sage: t[:], s[:] ( [ 1 -2 3] [ 0 -3 -2] [ 4 5 6] [ 3 0 -1] [ 7 8 -9], [ 2 1 0] ) sage: all(s[i,j] == 1/2*(t[i,j]-t[j,i]) # Check: ....: for i in range(3) for j in range(3)) True sage: s.antisymmetrize() == s # another test True sage: t.antisymmetrize() == t.antisymmetrize(0,1) True
Antisymmetrization of a tensor of type \((0, 3)\) over the first two arguments:
sage: t = M.tensor((0,3)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], ....: [[10,-11,12], [13,14,-15], [16,17,18]], ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.antisymmetrize(0,1) ; s # (0,1) = the first two arguments Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) sage: s[:] [[[0, 0, 0], [-7, 8, -3], [-6, 14, 6]], [[7, -8, 3], [0, 0, 0], [19, -3, -3]], [[6, -14, -6], [-19, 3, 3], [0, 0, 0]]] sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[j,i,k]) # Check: ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.antisymmetrize(0,1) == s # another test True sage: s.symmetrize(0,1) == 0 # of course True
Instead of invoking the method
antisymmetrize()
, one can use the index notation with square brackets denoting the antisymmetrization; it suffices to pass the indices as a string inside square brackets:sage: s1 = t['_[ij]k'] ; s1 Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s1.symmetries() no symmetry; antisymmetry: (0, 1) sage: s1 == s True
The LaTeX notation is recognized:
sage: t['_{[ij]k}'] == s True
Note that in the index notation, the name of the indices is irrelevant; they can even be replaced by dots:
sage: t['_[..].'] == s True
Antisymmetrization of a tensor of type \((0,3)\) over the first and last arguments:
sage: s = t.antisymmetrize(0,2) ; s # (0,2) = first and last arguments Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 2) sage: s[:] [[[0, -4, -8], [0, -4, 14], [0, -4, -17]], [[4, 0, 16], [4, 0, -19], [4, 0, -4]], [[8, -16, 0], [-14, 19, 0], [17, 4, 0]]] sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[k,j,i]) # Check: ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.antisymmetrize(0,2) == s # another test True sage: s.symmetrize(0,2) == 0 # of course True sage: s.symmetrize(0,1) == 0 # no reason for this to hold False
Antisymmetrization of a tensor of type \((0,3)\) over the last two arguments:
sage: s = t.antisymmetrize(1,2) ; s # (1,2) = the last two arguments Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (1, 2) sage: s[:] [[[0, 3, -2], [-3, 0, -1], [2, 1, 0]], [[0, -12, -2], [12, 0, -16], [2, 16, 0]], [[0, 1, -23], [-1, 0, -1], [23, 1, 0]]] sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[i,k,j]) # Check: ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.antisymmetrize(1,2) == s # another test True sage: s.symmetrize(1,2) == 0 # of course True
The index notation can be used instead of the explicit call to
antisymmetrize()
:sage: t['_i[jk]'] == t.antisymmetrize(1,2) True
Full antisymmetrization of a tensor of type \((0,3)\):
sage: s = t.antisymmetrize() ; s Alternating form of degree 3 on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1, 2) sage: s[:] [[[0, 0, 0], [0, 0, 2/3], [0, -2/3, 0]], [[0, 0, -2/3], [0, 0, 0], [2/3, 0, 0]], [[0, 2/3, 0], [-2/3, 0, 0], [0, 0, 0]]] sage: all(s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k] ....: +t[k,i,j]-t[k,j,i]) ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.antisymmetrize() == s # another test True sage: s.symmetrize(0,1) == 0 # of course True sage: s.symmetrize(0,2) == 0 # of course True sage: s.symmetrize(1,2) == 0 # of course True sage: t.antisymmetrize() == t.antisymmetrize(0,1,2) True
The index notation can be used instead of the explicit call to
antisymmetrize()
:sage: t['_[ijk]'] == t.antisymmetrize() True sage: t['_[abc]'] == t.antisymmetrize() True sage: t['_[...]'] == t.antisymmetrize() True sage: t['_{[ijk]}'] == t.antisymmetrize() # LaTeX notation True
Antisymmetrization can be performed only on arguments on the same type:
sage: t = M.tensor((1,2)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], ....: [[10,-11,12], [13,14,-15], [16,17,18]], ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.antisymmetrize(0,1) Traceback (most recent call last): ... TypeError: 0 is a contravariant position, while 1 is a covariant position; antisymmetrization is meaningful only on tensor arguments of the same type sage: s = t.antisymmetrize(1,2) # OK: both 1 and 2 are covariant positions
The order of positions does not matter:
sage: t.antisymmetrize(2,1) == t.antisymmetrize(1,2) True
Again, the index notation can be used:
sage: t['^i_[jk]'] == t.antisymmetrize(1,2) True sage: t['^i_{[jk]}'] == t.antisymmetrize(1,2) # LaTeX notation True
The character ‘^’ can be skipped:
sage: t['i_[jk]'] == t.antisymmetrize(1,2) True
- base_module()#
Return the module on which
self
is defined.OUTPUT:
instance of
FiniteRankFreeModule
representing the free module on which the tensor is defined.
EXAMPLES:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.an_element().base_module() Rank-3 free module M over the Integer Ring sage: t = M.tensor((2,1)) sage: t.base_module() Rank-3 free module M over the Integer Ring sage: t.base_module() is M True
- common_basis(other)#
Find a common basis for the components of
self
andother
.In case of multiple common bases, the free module’s default basis is privileged. If the current components of
self
andother
are all relative to different bases, a common basis is searched by performing a component transformation, via the transformations listed inself._fmodule._basis_changes
, still privileging transformations to the free module’s default basis.INPUT:
other
– a tensor (instance ofFreeModuleTensor
)
OUTPUT:
instance of
FreeModuleBasis
representing the common basis; if no common basis is found,None
is returned
EXAMPLES:
Common basis for the components of two module elements:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: u = M([2,1,-5]) sage: f = M.basis('f') sage: M._basis_changes.clear() # to ensure that bases e and f are unrelated at this stage sage: v = M([0,4,2], basis=f) sage: u.common_basis(v)
The above result is
None
sinceu
andv
have been defined on different bases and no connection between these bases have been set:sage: list(u._components) [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring] sage: list(v._components) [Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
Linking bases
e
andf
changes the result:sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: M.set_change_of_basis(e, f, a) sage: u.common_basis(v) Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring
Indeed, v is now known in basis e:
sage: sorted(v._components, key=repr) [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring, Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
- comp(basis=None, from_basis=None)#
Return the components of
self
w.r.t to a given module basis.If the components are not known already, they are computed by the tensor change-of-basis formula from components in another basis.
INPUT:
basis
– (default:None
) basis in which the components are required; if none is provided, the components are assumed to refer to the module’s default basisfrom_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 basisbasis
; if none, a basis from which both the components and a change-of-basis tobasis
are known is selected.
OUTPUT:
components in the basis
basis
, as an instance of the classComponents
EXAMPLES:
Components of a tensor of type-\((1,1)\):
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 sage: t.components() 2-indices components w.r.t. Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: t.components() is t.components(e) # since e is M's default basis True sage: t.components()[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2]
A shortcut is
t.comp()
:sage: t.comp() is t.components() True
A direct access to the components w.r.t. the module’s default basis is provided by the square brackets applied to the tensor itself:
sage: t[1,2] is t.comp(e)[1,2] True sage: t[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2]
Components computed via a change-of-basis formula:
sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: f = e.new_basis(a, 'f') sage: t.comp(f) 2-indices components w.r.t. Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring sage: t.comp(f)[:] [ 0 0 0] [ 0 2 0] [-3 0 0]
- components(basis=None, from_basis=None)#
Return the components of
self
w.r.t to a given module basis.If the components are not known already, they are computed by the tensor change-of-basis formula from components in another basis.
INPUT:
basis
– (default:None
) basis in which the components are required; if none is provided, the components are assumed to refer to the module’s default basisfrom_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 basisbasis
; if none, a basis from which both the components and a change-of-basis tobasis
are known is selected.
OUTPUT:
components in the basis
basis
, as an instance of the classComponents
EXAMPLES:
Components of a tensor of type-\((1,1)\):
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 sage: t.components() 2-indices components w.r.t. Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: t.components() is t.components(e) # since e is M's default basis True sage: t.components()[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2]
A shortcut is
t.comp()
:sage: t.comp() is t.components() True
A direct access to the components w.r.t. the module’s default basis is provided by the square brackets applied to the tensor itself:
sage: t[1,2] is t.comp(e)[1,2] True sage: t[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2]
Components computed via a change-of-basis formula:
sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: f = e.new_basis(a, 'f') sage: t.comp(f) 2-indices components w.r.t. Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring sage: t.comp(f)[:] [ 0 0 0] [ 0 2 0] [-3 0 0]
- contract(*args)#
Contraction on one or more indices with another tensor.
INPUT:
pos1
– positions of the indices inself
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; ifpos1
is not provided, a single contraction on the last index position ofself
is assumedother
– the tensor to contract withpos2
– positions of the indices inother
involved in the contraction, with the same conventions as forpos1
; ifpos2
is not provided, a single contraction on the first index position ofother
is assumed
OUTPUT:
tensor resulting from the contraction at the positions
pos1
andpos2
ofself
withother
EXAMPLES:
Contraction of a tensor of type \((0,1)\) with a tensor of type \((1,0)\):
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form() # tensor of type (0,1) is a linear form sage: a[:] = [-3,2,1] sage: b = M([2,5,-2]) # tensor of type (1,0) is a module element sage: s = a.contract(b) ; s 2 sage: s in M.base_ring() True sage: s == a[0]*b[0] + a[1]*b[1] + a[2]*b[2] # check of the computation True
The positions of the contraction indices can be set explicitly:
sage: s == a.contract(0, b, 0) True sage: s == a.contract(0, b) True sage: s == a.contract(b, 0) True
Instead of the explicit call to the method
contract()
, the index notation can be used to specify the contraction, via Einstein convention (summation on repeated indices); it suffices to pass the indices as a string inside square brackets:sage: s1 = a['_i']*b['^i'] ; s1 2 sage: s1 == s True
In the present case, performing the contraction is identical to applying the linear form to the module element:
sage: a.contract(b) == a(b) True
or to applying the module element, considered as a tensor of type \((1,0)\), to the linear form:
sage: a.contract(b) == b(a) True
We have also:
sage: a.contract(b) == b.contract(a) True
Contraction of a tensor of type \((1,1)\) with a tensor of type \((1,0)\):
sage: a = M.tensor((1,1)) sage: a[:] = [[-1,2,3],[4,-5,6],[7,8,9]] sage: s = a.contract(b) ; s Element of the Rank-3 free module M over the Integer Ring sage: s.display() 2 e_0 - 29 e_1 + 36 e_2
Since the index positions have not been specified, the contraction takes place on the last position of a (i.e. no. 1) and the first position of
b
(i.e. no. 0):sage: a.contract(b) == a.contract(1, b, 0) True sage: a.contract(b) == b.contract(0, a, 1) True sage: a.contract(b) == b.contract(a, 1) True
Using the index notation with Einstein convention:
sage: a['^i_j']*b['^j'] == a.contract(b) True
The index
i
can be replaced by a dot:sage: a['^._j']*b['^j'] == a.contract(b) True
and the symbol
^
may be omitted, the distinction between contravariant and covariant indices being the position with respect to the symbol_
:sage: a['._j']*b['j'] == a.contract(b) True
Contraction is possible only between a contravariant index and a covariant one:
sage: a.contract(0, b) Traceback (most recent call last): ... TypeError: contraction on two contravariant indices not permitted
Contraction of a tensor of type \((2,1)\) with a tensor of type \((0,2)\):
sage: a = a*b ; a Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: b = M.tensor((0,2)) sage: b[:] = [[-2,3,1], [0,-2,3], [4,-7,6]] sage: s = a.contract(1, b, 1) ; s Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s[:] [[[-9, 16, 39], [18, -32, -78], [27, -48, -117]], [[36, -64, -156], [-45, 80, 195], [54, -96, -234]], [[63, -112, -273], [72, -128, -312], [81, -144, -351]]]
Check of the computation:
sage: all(s[i,j,k] == a[i,0,j]*b[k,0]+a[i,1,j]*b[k,1]+a[i,2,j]*b[k,2] ....: for i in range(3) for j in range(3) for k in range(3)) True
Using index notation:
sage: a['il_j']*b['_kl'] == a.contract(1, b, 1) True
LaTeX notation are allowed:
sage: a['^{il}_j']*b['_{kl}'] == a.contract(1, b, 1) True
Indices not involved in the contraction may be replaced by dots:
sage: a['.l_.']*b['_.l'] == a.contract(1, b, 1) True
The two tensors do not have to be defined on the same basis for the contraction to take place, reflecting the fact that the contraction is basis-independent:
sage: A = M.automorphism() sage: A[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: h = e.new_basis(A, 'h') sage: b.comp(h)[:] # forces the computation of b's components w.r.t. basis h [-2 -3 0] [ 7 6 -4] [ 3 -1 -2] sage: b.del_other_comp(h) # deletes components w.r.t. basis e sage: list(b._components) # indeed: [Basis (h_0,h_1,h_2) on the Rank-3 free module M over the Integer Ring] sage: list(a._components) # while a is known only in basis e: [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring] sage: s1 = a.contract(1, b, 1) ; s1 # yet the computation is possible Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s1 == s # ... and yields the same result as previously: True
The contraction can be performed on more than a single index; for instance a \(2\)-indices contraction of a type-\((2,1)\) tensor with a type-\((1,2)\) one is:
sage: a # a is a tensor of type-(2,1) Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: b = M([1,-1,2])*b ; b # a tensor of type (1,2) Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s = a.contract(1,2,b,1,0) ; s # the double contraction Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring sage: s[:] [ -36 30 15] [-252 210 105] [-204 170 85] sage: s == a['^.k_l']*b['^l_k.'] # the same thing in index notation True
- copy(name=None, latex_name=None)#
Return an exact copy of
self
.The name and the derived quantities are not copied.
INPUT:
name
– (default:None
) name given to the copylatex_name
– (default:None
) LaTeX symbol to denote the copy; if none is provided, the LaTeX symbol is set toname
EXAMPLES:
Copy of a tensor of type \((1,1)\):
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 sage: t1 = t.copy() sage: t1[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2] sage: t1 == t True
If the original tensor is modified, the copy is not:
sage: t[2,2] = 4 sage: t1[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2] sage: t1 == t False
- copy_from(other)#
Make
self
to a copy fromother
.INPUT:
other
– other tensor in the very same module from whichself
should be a copy of
Warning
All previous defined components will be deleted!
EXAMPLES:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 sage: s = M.tensor((1,1), name='s') sage: s.copy_from(t) sage: s[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2] sage: s == t True
If the original tensor is modified, the copy is not:
sage: t[2,2] = 4 sage: s[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2] sage: s == t False
- del_other_comp(basis=None)#
Delete all the components but those corresponding to
basis
.INPUT:
basis
– (default:None
) basis in which the components are kept; if none the module’s default basis is assumed
EXAMPLES:
Deleting components of a module element:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: u = M([2,1,-5]) sage: f = M.basis('f') sage: u.add_comp(f)[:] = [0,4,2] sage: sorted(u._components, key=repr) [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring, Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring] sage: u.del_other_comp(f) sage: list(u._components) [Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring]
Let us restore the components w.r.t. e and delete those w.r.t. f:
sage: u.add_comp(e)[:] = [2,1,-5] sage: sorted(u._components, key=repr) [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring, Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring] sage: u.del_other_comp() # default argument: basis = e sage: list(u._components) [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring]
- disp(basis=None, format_spec=None)#
Display
self
in terms of its expansion w.r.t. a given module basis.The expansion is actually performed onto tensor products of elements of the given basis and of elements of its dual basis (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode).
INPUT:
basis
– (default:None
) basis of the free module with respect to which the tensor is expanded; if none is provided, the module’s default basis is assumedformat_spec
– (default:None
) format specification passed toself._fmodule._output_formatter
to format the output
EXAMPLES:
Display of a module element (type-\((1,0)\) tensor):
sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') ; e Basis (e_1,e_2) on the 2-dimensional vector space M over the Rational Field sage: v = M([1/3,-2], name='v') sage: v.display(e) v = 1/3 e_1 - 2 e_2 sage: v.display() # a shortcut since e is M's default basis v = 1/3 e_1 - 2 e_2 sage: latex(v.display()) # display in the notebook v = \frac{1}{3} e_{1} -2 e_{2}
A shortcut is
disp()
:sage: v.disp() v = 1/3 e_1 - 2 e_2
Display of a linear form (type-\((0,1)\) tensor):
sage: de = e.dual_basis() ; de Dual basis (e^1,e^2) on the 2-dimensional vector space M over the Rational Field sage: w = - 3/4 * de[1] + de[2] ; w Linear form on the 2-dimensional vector space M over the Rational Field sage: w.set_name('w', latex_name='\\omega') sage: w.display() w = -3/4 e^1 + e^2 sage: latex(w.display()) # display in the notebook \omega = -\frac{3}{4} e^{1} +e^{2}
Display of a type-\((1,1)\) tensor:
sage: t = v*w ; t # the type-(1,1) is formed as the tensor product of v by w Type-(1,1) tensor v⊗w on the 2-dimensional vector space M over the Rational Field sage: t.display() v⊗w = -1/4 e_1⊗e^1 + 1/3 e_1⊗e^2 + 3/2 e_2⊗e^1 - 2 e_2⊗e^2 sage: latex(t.display()) # display in the notebook v\otimes \omega = -\frac{1}{4} e_{1}\otimes e^{1} + \frac{1}{3} e_{1}\otimes e^{2} + \frac{3}{2} e_{2}\otimes e^{1} -2 e_{2}\otimes e^{2}
Display in a basis which is not the default one:
sage: a = M.automorphism(matrix=[[1,2],[3,4]], basis=e) sage: f = e.new_basis(a, 'f') sage: v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a v = -8/3 f_1 + 3/2 f_2 sage: w.display(f) w = 9/4 f^1 + 5/2 f^2 sage: t.display(f) v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2
Parallel computation:
sage: Parallelism().set('tensor', nproc=2) sage: t2 = v*w sage: t2.display(f) v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2 sage: t2[f,:] == t[f,:] # check of the parallel computation True sage: Parallelism().set('tensor', nproc=1) # switch off parallelization
The output format can be set via the argument
output_formatter
passed at the module construction:sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1, ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: v = N([1/3,-2], name='v') sage: v.display() # default format (53 bits of precision) v = 0.333333333333333 e_1 - 2.00000000000000 e_2 sage: latex(v.display()) v = 0.333333333333333 e_{1} -2.00000000000000 e_{2}
The output format is then controlled by the argument
format_spec
of the methoddisplay()
:sage: v.display(format_spec=10) # 10 bits of precision v = 0.33 e_1 - 2.0 e_2
Check that the bug reported in trac ticket #22520 is fixed:
sage: M = FiniteRankFreeModule(SR, 3, name='M') # optional - sage.symbolic sage: e = M.basis('e') # optional - sage.symbolic sage: t = SR.var('t', domain='real') # optional - sage.symbolic sage: (t*e[0]).display() # optional - sage.symbolic t e_0
- display(basis=None, format_spec=None)#
Display
self
in terms of its expansion w.r.t. a given module basis.The expansion is actually performed onto tensor products of elements of the given basis and of elements of its dual basis (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode).
INPUT:
basis
– (default:None
) basis of the free module with respect to which the tensor is expanded; if none is provided, the module’s default basis is assumedformat_spec
– (default:None
) format specification passed toself._fmodule._output_formatter
to format the output
EXAMPLES:
Display of a module element (type-\((1,0)\) tensor):
sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') ; e Basis (e_1,e_2) on the 2-dimensional vector space M over the Rational Field sage: v = M([1/3,-2], name='v') sage: v.display(e) v = 1/3 e_1 - 2 e_2 sage: v.display() # a shortcut since e is M's default basis v = 1/3 e_1 - 2 e_2 sage: latex(v.display()) # display in the notebook v = \frac{1}{3} e_{1} -2 e_{2}
A shortcut is
disp()
:sage: v.disp() v = 1/3 e_1 - 2 e_2
Display of a linear form (type-\((0,1)\) tensor):
sage: de = e.dual_basis() ; de Dual basis (e^1,e^2) on the 2-dimensional vector space M over the Rational Field sage: w = - 3/4 * de[1] + de[2] ; w Linear form on the 2-dimensional vector space M over the Rational Field sage: w.set_name('w', latex_name='\\omega') sage: w.display() w = -3/4 e^1 + e^2 sage: latex(w.display()) # display in the notebook \omega = -\frac{3}{4} e^{1} +e^{2}
Display of a type-\((1,1)\) tensor:
sage: t = v*w ; t # the type-(1,1) is formed as the tensor product of v by w Type-(1,1) tensor v⊗w on the 2-dimensional vector space M over the Rational Field sage: t.display() v⊗w = -1/4 e_1⊗e^1 + 1/3 e_1⊗e^2 + 3/2 e_2⊗e^1 - 2 e_2⊗e^2 sage: latex(t.display()) # display in the notebook v\otimes \omega = -\frac{1}{4} e_{1}\otimes e^{1} + \frac{1}{3} e_{1}\otimes e^{2} + \frac{3}{2} e_{2}\otimes e^{1} -2 e_{2}\otimes e^{2}
Display in a basis which is not the default one:
sage: a = M.automorphism(matrix=[[1,2],[3,4]], basis=e) sage: f = e.new_basis(a, 'f') sage: v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a v = -8/3 f_1 + 3/2 f_2 sage: w.display(f) w = 9/4 f^1 + 5/2 f^2 sage: t.display(f) v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2
Parallel computation:
sage: Parallelism().set('tensor', nproc=2) sage: t2 = v*w sage: t2.display(f) v⊗w = -6 f_1⊗f^1 - 20/3 f_1⊗f^2 + 27/8 f_2⊗f^1 + 15/4 f_2⊗f^2 sage: t2[f,:] == t[f,:] # check of the parallel computation True sage: Parallelism().set('tensor', nproc=1) # switch off parallelization
The output format can be set via the argument
output_formatter
passed at the module construction:sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1, ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: v = N([1/3,-2], name='v') sage: v.display() # default format (53 bits of precision) v = 0.333333333333333 e_1 - 2.00000000000000 e_2 sage: latex(v.display()) v = 0.333333333333333 e_{1} -2.00000000000000 e_{2}
The output format is then controlled by the argument
format_spec
of the methoddisplay()
:sage: v.display(format_spec=10) # 10 bits of precision v = 0.33 e_1 - 2.0 e_2
Check that the bug reported in trac ticket #22520 is fixed:
sage: M = FiniteRankFreeModule(SR, 3, name='M') # optional - sage.symbolic sage: e = M.basis('e') # optional - sage.symbolic sage: t = SR.var('t', domain='real') # optional - sage.symbolic sage: (t*e[0]).display() # optional - sage.symbolic 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; ifNone
, the module’s default basis is assumedformat_spec
– (default:None
) format specification passed toself._fmodule._output_formatter
to format the outputsymbol
– (default:None
) string (typically a single letter) specifying the symbol for the components; ifNone
, the tensor name is used if it has been set, otherwise'X'
is usedlatex_symbol
– (default:None
) string specifying the LaTeX symbol for the components; ifNone
, the tensor LaTeX name is used if it has been set, otherwise'X'
is usedindex_labels
– (default:None
) list of strings representing the labels of each of the individual indices; ifNone
, integer labels are usedindex_latex_labels
– (default:None
) list of strings representing the LaTeX labels of each of the individual indices; ifNone
, integers labels are usedonly_nonzero
– (default:True
) boolean; ifTrue
, only nonzero components are displayedonly_nonredundant
– (default:False
) boolean; ifTrue
, only nonredundant components are displayed in case of symmetries
EXAMPLES:
Display of the components of a type-\((2,1)\) tensor on a rank 2 vector space over \(\QQ\):
sage: FiniteRankFreeModule._clear_cache_() # for doctests only sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((2,1), name='T', sym=(0,1)) sage: t[1,2,1], t[1,2,2], t[2,2,2] = 2/3, -1/4, 3 sage: t.display() T = 2/3 e_1⊗e_2⊗e^1 - 1/4 e_1⊗e_2⊗e^2 + 2/3 e_2⊗e_1⊗e^1 - 1/4 e_2⊗e_1⊗e^2 + 3 e_2⊗e_2⊗e^2 sage: t.display_comp() T^12_1 = 2/3 T^12_2 = -1/4 T^21_1 = 2/3 T^21_2 = -1/4 T^22_2 = 3
The LaTeX output for the notebook:
sage: latex(t.display_comp()) \begin{array}{lcl} T_{\phantom{\, 1}\phantom{\, 2}\,1}^{\,1\,2\phantom{\, 1}} & = & \frac{2}{3} \\ T_{\phantom{\, 1}\phantom{\, 2}\,2}^{\,1\,2\phantom{\, 2}} & = & -\frac{1}{4} \\ T_{\phantom{\, 2}\phantom{\, 1}\,1}^{\,2\,1\phantom{\, 1}} & = & \frac{2}{3} \\ T_{\phantom{\, 2}\phantom{\, 1}\,2}^{\,2\,1\phantom{\, 2}} & = & -\frac{1}{4} \\ T_{\phantom{\, 2}\phantom{\, 2}\,2}^{\,2\,2\phantom{\, 2}} & = & 3 \end{array}
By default, only the non-vanishing components are displayed; to see all the components, the argument
only_nonzero
must be set toFalse
:sage: t.display_comp(only_nonzero=False) T^11_1 = 0 T^11_2 = 0 T^12_1 = 2/3 T^12_2 = -1/4 T^21_1 = 2/3 T^21_2 = -1/4 T^22_1 = 0 T^22_2 = 3
t
being symmetric w.r.t. to its first two indices, one may ask to skip the components that can be deduced by symmetry:sage: t.display_comp(only_nonredundant=True) T^12_1 = 2/3 T^12_2 = -1/4 T^22_2 = 3
The index symbols can be customized:
sage: t.display_comp(index_labels=['x', 'y']) T^xy_x = 2/3 T^xy_y = -1/4 T^yx_x = 2/3 T^yx_y = -1/4 T^yy_y = 3
Display of the components w.r.t. a basis different from the default one:
sage: f = M.basis('f', from_family=(-e[1]+e[2], e[1]+e[2])) sage: t.display_comp(basis=f) T^11_1 = 29/24 T^11_2 = 13/24 T^12_1 = 3/4 T^12_2 = 3/4 T^21_1 = 3/4 T^21_2 = 3/4 T^22_1 = 7/24 T^22_2 = 23/24
- pick_a_basis()#
Return a basis in which the tensor components are defined.
The free module’s default basis is privileged.
OUTPUT:
instance of
FreeModuleBasis
representing the basis
EXAMPLES:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') sage: t[0,1] = 4 # component set in the default basis (e) sage: t.pick_a_basis() Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: f = M.basis('f') sage: t.add_comp(f)[2,1] = -4 # the components in basis e are not erased sage: t.pick_a_basis() Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: t.set_comp(f)[2,1] = -4 # the components in basis e not erased sage: t.pick_a_basis() Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring
- set_comp(basis=None)#
Return the components of
self
w.r.t. a given module basis for assignment.The components with respect to other bases are deleted, in order to avoid any inconsistency. To keep them, use the method
add_comp()
instead.INPUT:
basis
– (default:None
) basis in which the components are defined; if none is provided, the components are assumed to refer to the module’s default basis
OUTPUT:
components in the given basis, as an instance of the class
Components
; if such components did not exist previously, they are created.
EXAMPLES:
Setting components of a type-\((1,1)\) tensor:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t.set_comp()[0,1] = -3 sage: t.display() t = -3 e_0⊗e^1 sage: t.set_comp()[1,2] = 2 sage: t.display() t = -3 e_0⊗e^1 + 2 e_1⊗e^2 sage: t.set_comp(e) 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
Setting components in a new basis:
sage: f = M.basis('f') sage: t.set_comp(f)[0,1] = 4 sage: list(t._components) # the components w.r.t. basis e have been deleted [Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] sage: t.display(f) t = 4 f_0⊗f^1
The components w.r.t. basis e can be deduced from those w.r.t. basis f, once a relation between the two bases has been set:
sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: M.set_change_of_basis(e, f, a) sage: t.display(e) t = -4 e_1⊗e^2 sage: sorted(t._components, key=repr) [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring]
Since zero is an immutable element, its components cannot be changed:
sage: z = M.tensor_module(1, 1).zero() sage: z.set_comp(e)[0,1] = 1 Traceback (most recent call last): ... ValueError: the components of an immutable element cannot be changed
- set_name(name=None, latex_name=None)#
Set (or change) the text name and LaTeX name of
self
.INPUT:
name
– (default:None
) string; name given to the tensorlatex_name
– (default:None
) string; LaTeX symbol to denote the tensor; if None whilename
is provided, the LaTeX symbol is set toname
EXAMPLES:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1)) ; t Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: t.set_name('t') ; t Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring sage: latex(t) t sage: t.set_name(latex_name=r'\tau') ; t Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring sage: latex(t) \tau
- symmetries()#
Print the list of symmetries and antisymmetries of
self
.EXAMPLES:
Various symmetries / antisymmetries for a rank-4 tensor:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((4,0), name='T') # no symmetry declared sage: t.symmetries() no symmetry; no antisymmetry sage: t = M.tensor((4,0), name='T', sym=(0,1)) sage: t.symmetries() symmetry: (0, 1); no antisymmetry sage: t = M.tensor((4,0), name='T', sym=[(0,1), (2,3)]) sage: t.symmetries() symmetries: [(0, 1), (2, 3)]; no antisymmetry sage: t = M.tensor((4,0), name='T', sym=(0,1), antisym=(2,3)) sage: t.symmetries() symmetry: (0, 1); antisymmetry: (2, 3)
- symmetrize(*pos, **kwargs)#
Symmetrization over some arguments.
INPUT:
pos
– list of argument positions involved in the symmetrization (with the conventionposition=0
for the first argument); if none, the symmetrization is performed over all the argumentsbasis
– (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: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,0)) sage: t[:] = [[2,1,-3],[0,-4,5],[-1,4,2]] sage: s = t.symmetrize() ; s Type-(2,0) tensor on the 3-dimensional vector space M over the Rational Field sage: t[:], s[:] ( [ 2 1 -3] [ 2 1/2 -2] [ 0 -4 5] [1/2 -4 9/2] [-1 4 2], [ -2 9/2 2] ) sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: all(s[i,j] == 1/2*(t[i,j]+t[j,i]) # check: ....: for i in range(3) for j in range(3)) True
Instead of invoking the method
symmetrize()
, one may use the index notation with parentheses to denote the symmetrization; it suffices to pass the indices as a string inside square brackets:sage: t['(ij)'] Type-(2,0) tensor on the 3-dimensional vector space M over the Rational Field sage: t['(ij)'].symmetries() symmetry: (0, 1); no antisymmetry sage: t['(ij)'] == t.symmetrize() True
The indices names are not significant; they can even be replaced by dots:
sage: t['(..)'] == t.symmetrize() True
The LaTeX notation can be used as well:
sage: t['^{(ij)}'] == t.symmetrize() True
Symmetrization of a tensor of type \((0,3)\) on the first two arguments:
sage: t = M.tensor((0,3)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], ....: [[10,-11,12], [13,14,-15], [16,17,18]], ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.symmetrize(0,1) ; s # (0,1) = the first two arguments Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: s[:] [[[1, 2, 3], [3, -3, 9], [13, -6, -15]], [[3, -3, 9], [13, 14, -15], [-3, 20, 21]], [[13, -6, -15], [-3, 20, 21], [25, 26, -27]]] sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[j,i,k]) # Check: ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.symmetrize(0,1) == s # another test True
Again the index notation can be used:
sage: t['_(ij)k'] == t.symmetrize(0,1) True sage: t['_(..).'] == t.symmetrize(0,1) # no index name True sage: t['_{(ij)k}'] == t.symmetrize(0,1) # LaTeX notation True sage: t['_{(..).}'] == t.symmetrize(0,1) # this also allowed True
Symmetrization of a tensor of type \((0,3)\) on the first and last arguments:
sage: s = t.symmetrize(0,2) ; s # (0,2) = first and last arguments Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() symmetry: (0, 2); no antisymmetry sage: s[:] [[[1, 6, 11], [-4, 9, -8], [7, 12, 8]], [[6, -11, -4], [9, 14, 4], [12, 17, 22]], [[11, -4, -21], [-8, 4, 24], [8, 22, -27]]] sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[k,j,i]) ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.symmetrize(0,2) == s # another test True
Symmetrization of a tensor of type \((0,3)\) on the last two arguments:
sage: s = t.symmetrize(1,2) ; s # (1,2) = the last two arguments Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() symmetry: (1, 2); no antisymmetry sage: s[:] [[[1, -1, 5], [-1, 5, 7], [5, 7, -9]], [[10, 1, 14], [1, 14, 1], [14, 1, 18]], [[19, -21, 2], [-21, 23, 25], [2, 25, -27]]] sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[i,k,j]) # Check: ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.symmetrize(1,2) == s # another test True
Use of the index notation:
sage: t['_i(jk)'] == t.symmetrize(1,2) True sage: t['_.(..)'] == t.symmetrize(1,2) True sage: t['_{i(jk)}'] == t.symmetrize(1,2) # LaTeX notation True
Full symmetrization of a tensor of type \((0,3)\):
sage: s = t.symmetrize() ; s Type-(0,3) tensor on the 3-dimensional vector space M over the Rational Field sage: s.symmetries() symmetry: (0, 1, 2); no antisymmetry sage: s[:] [[[1, 8/3, 29/3], [8/3, 7/3, 0], [29/3, 0, -5/3]], [[8/3, 7/3, 0], [7/3, 14, 25/3], [0, 25/3, 68/3]], [[29/3, 0, -5/3], [0, 25/3, 68/3], [-5/3, 68/3, -27]]] sage: all(s[i,j,k] == 1/6*(t[i,j,k]+t[i,k,j]+t[j,k,i]+t[j,i,k]+t[k,i,j]+t[k,j,i]) # Check: ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.symmetrize() == s # another test True
Index notation for the full symmetrization:
sage: t['_(ijk)'] == t.symmetrize() True sage: t['_{(ijk)}'] == t.symmetrize() # LaTeX notation True
Symmetrization can be performed only on arguments on the same type:
sage: t = M.tensor((1,2)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], ....: [[10,-11,12], [13,14,-15], [16,17,18]], ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.symmetrize(0,1) Traceback (most recent call last): ... TypeError: 0 is a contravariant position, while 1 is a covariant position; symmetrization is meaningful only on tensor arguments of the same type sage: s = t.symmetrize(1,2) # OK: both 1 and 2 are covariant positions
The order of positions does not matter:
sage: t.symmetrize(2,1) == t.symmetrize(1,2) True
Use of the index notation:
sage: t['^i_(jk)'] == t.symmetrize(1,2) True sage: t['^._(..)'] == t.symmetrize(1,2) True
The character
^
can be skipped, the character_
being sufficient to separate contravariant indices from covariant ones:sage: t['i_(jk)'] == t.symmetrize(1,2) True
The LaTeX notation can be employed:
sage: t['^{i}_{(jk)}'] == t.symmetrize(1,2) True
- tensor_rank()#
Return the tensor rank of
self
.OUTPUT:
integer
k+l
, wherek
is the contravariant rank andl
is the covariant rank
EXAMPLES:
sage: M = FiniteRankFreeModule(ZZ, 3) sage: M.an_element().tensor_rank() 1 sage: t = M.tensor((2,1)) sage: t.tensor_rank() 3
- tensor_type()#
Return the tensor type of
self
.OUTPUT:
pair
(k, l)
, wherek
is the contravariant rank andl
is the covariant rank
EXAMPLES:
sage: M = FiniteRankFreeModule(ZZ, 3) sage: M.an_element().tensor_type() (1, 0) sage: t = M.tensor((2,1)) sage: t.tensor_type() (2, 1)
- trace(pos1=0, pos2=1)#
Trace (contraction) on two slots of the tensor.
INPUT:
pos1
– (default: 0) position of the first index for the contraction, with the conventionpos1=0
for the first slotpos2
– (default: 1) position of the second index for the contraction, with the same convention as forpos1
; the variance type ofpos2
must be opposite to that ofpos1
OUTPUT:
tensor or scalar resulting from the
(pos1, pos2)
contraction
EXAMPLES:
Trace of a type-\((1,1)\) tensor:
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a = M.tensor((1,1), name='a') ; a Type-(1,1) tensor a on the Rank-3 free module M over the Integer Ring sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: a.trace() 15 sage: a.trace(0,1) # equivalent to above (contraction of slot 0 with slot 1) 15 sage: a.trace(1,0) # the order of the slots does not matter 15
Instead of the explicit call to the method
trace()
, one may use the index notation with Einstein convention (summation over repeated indices); it suffices to pass the indices as a string inside square brackets:sage: a['^i_i'] 15
The letter ‘i’ to denote the repeated index can be replaced by any other letter:
sage: a['^s_s'] 15
Moreover, the symbol
^
can be omitted:sage: a['i_i'] 15
The contraction on two slots having the same tensor type cannot occur:
sage: b = M.tensor((2,0), name='b') ; b Type-(2,0) tensor b on the Rank-3 free module M over the Integer Ring sage: b[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: b.trace(0,1) Traceback (most recent call last): ... IndexError: contraction on two contravariant indices is not allowed
The contraction either preserves or destroys the symmetries:
sage: b = M.alternating_form(2, 'b') ; b Alternating form b of degree 2 on the Rank-3 free module M over the Integer Ring sage: b[0,1], b[0,2], b[1,2] = 3, 2, 1 sage: t = a*b ; t Type-(1,3) tensor a⊗b on the Rank-3 free module M over the Integer Ring
By construction,
t
is a tensor field antisymmetric w.r.t. its last two slots:sage: t.symmetries() no symmetry; antisymmetry: (2, 3) sage: s = t.trace(0,1) ; s # contraction on the first two slots Alternating form of degree 2 on the Rank-3 free module M over the Integer Ring sage: s.symmetries() # the antisymmetry is preserved no symmetry; antisymmetry: (0, 1) sage: s[:] [ 0 45 30] [-45 0 15] [-30 -15 0] sage: s == 15*b # check True sage: s = t.trace(0,2) ; s # contraction on the first and third slots Type-(0,2) tensor on the Rank-3 free module M over the Integer Ring sage: s.symmetries() # the antisymmetry has been destroyed by the above contraction: no symmetry; no antisymmetry sage: s[:] # indeed: [-26 -4 6] [-31 -2 9] [-36 0 12] sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) ....: for j in M.irange()] for i in M.irange()] ) # check True
Use of index notation instead of
trace()
:sage: t['^k_kij'] == t.trace(0,1) True sage: t['^k_{kij}'] == t.trace(0,1) # LaTeX notation True sage: t['^k_ikj'] == t.trace(0,2) True sage: t['^k_ijk'] == t.trace(0,3) True
Index symbols not involved in the contraction may be replaced by dots:
sage: t['^k_k..'] == t.trace(0,1) True sage: t['^k_.k.'] == t.trace(0,2) True sage: t['^k_..k'] == t.trace(0,3) True