Free module automorphisms

Given a free module \(M\) of finite rank over a commutative ring \(R\), an automorphism of \(M\) is a map

\[\phi:\ M \longrightarrow M\]

that is linear (i.e. is a module homomorphism) and bijective.

Automorphisms of a free module of finite rank are implemented via the class FreeModuleAutomorphism.

AUTHORS:

  • Eric Gourgoulhon (2015): initial version

REFERENCES:

  • Chaps. 15, 24 of R. Godement: Algebra, Hermann (Paris) / Houghton Mifflin (Boston) (1968)
class sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism(fmodule, name=None, latex_name=None, is_identity=False)

Bases: sage.tensor.modules.free_module_tensor.FreeModuleTensor, sage.structure.element.MultiplicativeGroupElement

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

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

This class inherits from the classes FreeModuleTensor and MultiplicativeGroupElement.

INPUT:

  • fmodule – free module \(M\) of finite rank over a commutative ring \(R\), as an instance of FiniteRankFreeModule
  • name – (default: None) name given to the automorphism
  • latex_name – (default: None) LaTeX symbol to denote the automorphism; if none is provided, the LaTeX symbol is set to name
  • is_identity – (default: False) determines whether the constructed object is the identity automorphism, i.e. the identity map of \(M\) considered as an automorphism (the identity element of the general linear group)

EXAMPLES:

Automorphism of a rank-2 free module over \(\ZZ\):

sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1)
sage: a = M.automorphism(name='a', latex_name=r'\alpha') ; a
Automorphism a of the Rank-2 free module M over the Integer Ring
sage: a.parent()
General linear group of the Rank-2 free module M over the Integer Ring
sage: a.parent() is M.general_linear_group()
True
sage: latex(a)
\alpha

Setting the components of a w.r.t. a basis of module M:

sage: e = M.basis('e') ; e
Basis (e_1,e_2) on the Rank-2 free module M over the Integer Ring
sage: a[:] = [[1,2],[1,3]]
sage: a.matrix(e)
[1 2]
[1 3]
sage: a(e[1]).display()
a(e_1) = e_1 + e_2
sage: a(e[2]).display()
a(e_2) = 2 e_1 + 3 e_2

Actually, the components w.r.t. a given basis can be specified at the construction of the object:

sage: a = M.automorphism(matrix=[[1,2],[1,3]], basis=e, name='a',
....:                    latex_name=r'\alpha') ; a
Automorphism a of the Rank-2 free module M over the Integer Ring
sage: a.matrix(e)
[1 2]
[1 3]

Since e is the module’s default basis, it can be omitted in the argument list:

sage: a == M.automorphism(matrix=[[1,2],[1,3]], name='a',
....:                     latex_name=r'\alpha')
True

The matrix of the automorphism can be obtained in any basis:

sage: f = M.basis('f', from_family=(3*e[1]+4*e[2], 5*e[1]+7*e[2])) ; f
Basis (f_1,f_2) on the Rank-2 free module M over the Integer Ring
sage: a.matrix(f)
[2 3]
[1 2]

Automorphisms are tensors of type \((1,1)\):

sage: a.tensor_type()
(1, 1)
sage: a.tensor_rank()
2

In particular, they can be displayed as such:

sage: a.display(e)
a = e_1*e^1 + 2 e_1*e^2 + e_2*e^1 + 3 e_2*e^2
sage: a.display(f)
a = 2 f_1*f^1 + 3 f_1*f^2 + f_2*f^1 + 2 f_2*f^2

The automorphism acting on a module element:

sage: v = M([-2,3], name='v') ; v
Element v of the Rank-2 free module M over the Integer Ring
sage: a(v)
Element a(v) of the Rank-2 free module M over the Integer Ring
sage: a(v).display()
a(v) = 4 e_1 + 7 e_2

A second automorphism of the module M:

sage: b = M.automorphism([[0,1],[-1,0]], name='b') ; b
Automorphism b of the Rank-2 free module M over the Integer Ring
sage: b.matrix(e)
[ 0  1]
[-1  0]
sage: b(e[1]).display()
b(e_1) = -e_2
sage: b(e[2]).display()
b(e_2) = e_1

The composition of automorphisms is performed via the multiplication operator:

sage: s = a*b ; s
Automorphism of the Rank-2 free module M over the Integer Ring
sage: s(v) == a(b(v))
True
sage: s.matrix(f)
[ 11  19]
[ -7 -12]
sage: s.matrix(f) == a.matrix(f) * b.matrix(f)
True

It is not commutative:

sage: a*b != b*a
True

In other words, the parent of a and b, i.e. the group \(\mathrm{GL}(M)\), is not abelian:

sage: M.general_linear_group() in CommutativeAdditiveGroups()
False

The neutral element for the composition law is the module identity map:

sage: id = M.identity_map() ; id
Identity map of the Rank-2 free module M over the Integer Ring
sage: id.parent()
General linear group of the Rank-2 free module M over the Integer Ring
sage: id(v) == v
True
sage: id.matrix(f)
[1 0]
[0 1]
sage: id*a == a
True
sage: a*id == a
True

The inverse of an automorphism is obtained via the method inverse(), or the operator ~, or the exponent -1:

sage: a.inverse()
Automorphism a^(-1) of the Rank-2 free module M over the Integer Ring
sage: a.inverse() is ~a
True
sage: a.inverse() is a^(-1)
True
sage: (a^(-1)).matrix(e)
[ 3 -2]
[-1  1]
sage: a*a^(-1) == id
True
sage: a^(-1)*a == id
True
sage: a^(-1)*s == b
True
sage: (a^(-1))(a(v)) == v
True

The module’s changes of basis are stored as automorphisms:

sage: M.change_of_basis(e,f)
Automorphism of the Rank-2 free module M over the Integer Ring
sage: M.change_of_basis(e,f).parent()
General linear group of the Rank-2 free module M over the Integer Ring
sage: M.change_of_basis(e,f).matrix(e)
[3 5]
[4 7]
sage: M.change_of_basis(f,e) == M.change_of_basis(e,f).inverse()
True

The opposite of an automorphism is still an automorphism:

sage: -a
Automorphism -a of the Rank-2 free module M over the Integer Ring
sage: (-a).parent()
General linear group of the Rank-2 free module M over the Integer Ring
sage: (-a).matrix(e) == - (a.matrix(e))
True

Adding two automorphisms results in a generic type-(1,1) tensor:

sage: s = a + b ; s
Type-(1,1) tensor a+b on the Rank-2 free module M over the Integer Ring
sage: s.parent()
Free module of type-(1,1) tensors on the Rank-2 free module M over the
 Integer Ring
sage: a[:], b[:], s[:]
(
[1 2]  [ 0  1]  [1 3]
[1 3], [-1  0], [0 3]
)

To get the result as an endomorphism, one has to explicitely convert it via the parent of endomorphisms, \(\mathrm{End}(M)\):

sage: s = End(M)(a+b) ; s
Generic endomorphism of Rank-2 free module M over the Integer Ring
sage: s(v) == a(v) + b(v)
True
sage: s.matrix(e) == a.matrix(e) + b.matrix(e)
True
sage: s.matrix(f) == a.matrix(f) + b.matrix(f)
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 automorphism has already components in other bases, it is the user’s responsability 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:

Adding components to an automorphism of a rank-3 free \(\ZZ\)-module:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e')
sage: a = M.automorphism(name='a')
sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]]
sage: f = M.basis('f', from_family=(-e[0], 3*e[1]+4*e[2],
....:                               5*e[1]+7*e[2])) ; f
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer
 Ring
sage: a.add_comp(f)[:] = [[1,0,0], [0, 80, 143], [0, -47, -84]]

The components in basis e have been kept:

sage: a._components # random (dictionary output)
{Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer
 Ring: 2-indices components w.r.t. 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: 2-indices components w.r.t. Basis (f_0,f_1,f_2) on the
 Rank-3 free module M over the Integer Ring}

For the identity map, it is not permitted to invoke add_comp():

sage: id = M.identity_map()
sage: id.add_comp(e)
Traceback (most recent call last):
...
TypeError: the components of the identity map cannot be changed

Indeed, the components are automatically set by a call to comp():

sage: id.comp(e)
Kronecker delta of size 3x3
sage: id.comp(f)
Kronecker delta of size 3x3
comp(basis=None, from_basis=None)

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

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

INPUT:

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

OUTPUT:

  • components in the basis basis, as an instance of the class Components, or, for the identity automorphism, of the subclass KroneckerDelta

EXAMPLES:

Components of an automorphism on a rank-3 free \(\ZZ\)-module:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: a = M.automorphism([[-1,0,0],[0,1,2],[0,1,3]], name='a')
sage: a.components(e)
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: a.components(e)[:]
[-1  0  0]
[ 0  1  2]
[ 0  1  3]

Since e is the module’s default basis, it can be omitted:

sage: a.components() is a.components(e)
True

A shortcut is a.comp():

sage: a.comp() is a.components()
True
sage: a.comp(e) is a.components()
True

Components in another basis:

sage: f1 = -e[2]
sage: f2 = 4*e[1] + 3*e[3]
sage: f3 = 7*e[1] + 5*e[3]
sage: f = M.basis('f', from_family=(f1,f2,f3))
sage: a.components(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: a.components(f)[:]
[  1  -6 -10]
[ -7  83 140]
[  4 -48 -81]

Some check of the above matrix:

sage: a(f[1]).display(f)
a(f_1) = f_1 - 7 f_2 + 4 f_3
sage: a(f[2]).display(f)
a(f_2) = -6 f_1 + 83 f_2 - 48 f_3
sage: a(f[3]).display(f)
a(f_3) = -10 f_1 + 140 f_2 - 81 f_3

Components of the identity map:

sage: id = M.identity_map()
sage: id.components(e)
Kronecker delta of size 3x3
sage: id.components(e)[:]
[1 0 0]
[0 1 0]
[0 0 1]
sage: id.components(f)
Kronecker delta of size 3x3
sage: id.components(f)[:]
[1 0 0]
[0 1 0]
[0 0 1]
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, or, for the identity automorphism, of the subclass KroneckerDelta

EXAMPLES:

Components of an automorphism on a rank-3 free \(\ZZ\)-module:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: a = M.automorphism([[-1,0,0],[0,1,2],[0,1,3]], name='a')
sage: a.components(e)
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: a.components(e)[:]
[-1  0  0]
[ 0  1  2]
[ 0  1  3]

Since e is the module’s default basis, it can be omitted:

sage: a.components() is a.components(e)
True

A shortcut is a.comp():

sage: a.comp() is a.components()
True
sage: a.comp(e) is a.components()
True

Components in another basis:

sage: f1 = -e[2]
sage: f2 = 4*e[1] + 3*e[3]
sage: f3 = 7*e[1] + 5*e[3]
sage: f = M.basis('f', from_family=(f1,f2,f3))
sage: a.components(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: a.components(f)[:]
[  1  -6 -10]
[ -7  83 140]
[  4 -48 -81]

Some check of the above matrix:

sage: a(f[1]).display(f)
a(f_1) = f_1 - 7 f_2 + 4 f_3
sage: a(f[2]).display(f)
a(f_2) = -6 f_1 + 83 f_2 - 48 f_3
sage: a(f[3]).display(f)
a(f_3) = -10 f_1 + 140 f_2 - 81 f_3

Components of the identity map:

sage: id = M.identity_map()
sage: id.components(e)
Kronecker delta of size 3x3
sage: id.components(e)[:]
[1 0 0]
[0 1 0]
[0 0 1]
sage: id.components(f)
Kronecker delta of size 3x3
sage: id.components(f)[:]
[1 0 0]
[0 1 0]
[0 0 1]
det()

Return the determinant of self.

OUTPUT:

  • element of the base ring of the module on which self is defined, equal to the determinant of self.

EXAMPLES:

Determinant of an automorphism on a \(\ZZ\)-module of rank 2:

sage: M = FiniteRankFreeModule(ZZ, 2, name='M')
sage: e = M.basis('e')
sage: a = M.automorphism([[4,7],[3,5]], name='a')
sage: a.matrix(e)
[4 7]
[3 5]
sage: a.det()
-1
sage: det(a)
-1
sage: ~a.det()  # determinant of the inverse automorphism
-1
sage: id = M.identity_map()
sage: id.det()
1
inverse()

Return the inverse automorphism.

OUTPUT:

EXAMPLES:

Inverse of an automorphism of a rank-3 free module:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e')
sage: a = M.automorphism(name='a')
sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]]
sage: a.inverse()
Automorphism a^(-1) of the Rank-3 free module M over the Integer
 Ring
sage: a.inverse().parent()
General linear group of the Rank-3 free module M over the Integer
 Ring

Check that a.inverse() is indeed the inverse automorphism:

sage: a.inverse() * a
Identity map of the Rank-3 free module M over the Integer Ring
sage: a * a.inverse()
Identity map of the Rank-3 free module M over the Integer Ring
sage: a.inverse().inverse() == a
True

Another check is:

sage: a.inverse().matrix(e)
[ 1  0  0]
[ 0 -3 -2]
[ 0 -1 -1]
sage: a.inverse().matrix(e) == (a.matrix(e))^(-1)
True

The inverse is cached (as long as a is not modified):

sage: a.inverse() is a.inverse()
True

If a is modified, the inverse is automatically recomputed:

sage: a[0,0] = -1
sage: a.matrix(e)
[-1  0  0]
[ 0 -1  2]
[ 0  1 -3]
sage: a.inverse().matrix(e)  # compare with above
[-1  0  0]
[ 0 -3 -2]
[ 0 -1 -1]

Shortcuts for inverse() are the operator ~ and the exponent -1:

sage: ~a is a.inverse()
True
sage: a^(-1) is a.inverse()
True

The inverse of the identity map is of course itself:

sage: id = M.identity_map()
sage: id.inverse() is id
True

and we have:

sage: a*a^(-1) == id
True
sage: a^(-1)*a == id
True
matrix(basis1=None, basis2=None)

Return the matrix of self w.r.t to a pair of bases.

If the matrix is not known already, it is computed from the matrix in another pair of bases by means of the change-of-basis formula.

INPUT:

  • basis1 – (default: None) basis of the free module on which self is defined; if none is provided, the module’s default basis is assumed
  • basis2 – (default: None) basis of the free module on which self is defined; if none is provided, basis2 is set to basis1

OUTPUT:

  • the matrix representing representing the automorphism self w.r.t to bases basis1 and basis2; more precisely, the columns of this matrix are formed by the components w.r.t. basis2 of the images of the elements of basis1.

EXAMPLES:

Matrices of an automorphism of a rank-3 free \(\ZZ\)-module:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: e = M.basis('e')
sage: a = M.automorphism([[-1,0,0],[0,1,2],[0,1,3]], name='a')
sage: a.matrix(e)
[-1  0  0]
[ 0  1  2]
[ 0  1  3]
sage: a.matrix()
[-1  0  0]
[ 0  1  2]
[ 0  1  3]
sage: f = M.basis('f', from_family=(-e[2], 4*e[1]+3*e[3],  7*e[1]+5*e[3])) ; f
Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring
sage: a.matrix(f)
[  1  -6 -10]
[ -7  83 140]
[  4 -48 -81]

Check of the above matrix:

sage: a(f[1]).display(f)
a(f_1) = f_1 - 7 f_2 + 4 f_3
sage: a(f[2]).display(f)
a(f_2) = -6 f_1 + 83 f_2 - 48 f_3
sage: a(f[3]).display(f)
a(f_3) = -10 f_1 + 140 f_2 - 81 f_3

Check of the change-of-basis formula:

sage: P = M.change_of_basis(e,f).matrix(e)
sage: a.matrix(f) == P^(-1) * a.matrix(e) * P
True

Check that the matrix of the product of two automorphisms is the product of their matrices:

sage: b = M.change_of_basis(e,f) ; b
Automorphism of the Rank-3 free module M over the Integer Ring
sage: b.matrix(e)
[ 0  4  7]
[-1  0  0]
[ 0  3  5]
sage: (a*b).matrix(e) == a.matrix(e) * b.matrix(e)
True

Check that the matrix of the inverse automorphism is the inverse of the automorphism’s matrix:

sage: (~a).matrix(e)
[-1  0  0]
[ 0  3 -2]
[ 0 -1  1]
sage: (~a).matrix(e) == ~(a.matrix(e))
True

Matrices of the identity map:

sage: id = M.identity_map()
sage: id.matrix(e)
[1 0 0]
[0 1 0]
[0 0 1]
sage: id.matrix(f)
[1 0 0]
[0 1 0]
[0 0 1]
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 the components of an automorphism of a rank-3 free \(\ZZ\)-module:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M')
sage: e = M.basis('e')
sage: a = M.automorphism(name='a')
sage: a.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
sage: a.set_comp(e)[:] = [[1,0,0],[0,1,2],[0,1,3]]
sage: a.matrix(e)
[1 0 0]
[0 1 2]
[0 1 3]

Since e is the module’s default basis, one has:

sage: a.set_comp() is a.set_comp(e)
True

The method set_comp() can be used to modify a single component:

sage: a.set_comp(e)[0,0] = -1
sage: a.matrix(e)
[-1  0  0]
[ 0  1  2]
[ 0  1  3]

A short cut to set_comp() is the bracket operator, with the basis as first argument:

sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]]
sage: a.matrix(e)
[ 1  0  0]
[ 0 -1  2]
[ 0  1 -3]
sage: a[e,0,0] = -1
sage: a.matrix(e)
[-1  0  0]
[ 0 -1  2]
[ 0  1 -3]

The call to set_comp() erases the components previously defined in other bases; to keep them, use the method add_comp() instead:

sage: f = M.basis('f', from_family=(-e[0], 3*e[1]+4*e[2],
....:                               5*e[1]+7*e[2])) ; f
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer
 Ring
sage: a._components
{Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer
 Ring: 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the
 Rank-3 free module M over the Integer Ring}
sage: a.set_comp(f)[:] = [[-1,0,0], [0,1,0], [0,0,-1]]

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

sage: a._components
{Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer
 Ring: 2-indices components w.r.t. Basis (f_0,f_1,f_2) on the
 Rank-3 free module M over the Integer Ring}

Of course, they can be computed from those in basis f by means of a change-of-basis formula, via the method comp() or matrix():

sage: a.matrix(e)
[ -1   0   0]
[  0  41 -30]
[  0  56 -41]

For the identity map, it is not permitted to set components:

sage: id = M.identity_map()
sage: id.set_comp(e)
Traceback (most recent call last):
...
TypeError: the components of the identity map cannot be changed

Indeed, the components are automatically set by a call to comp():

sage: id.comp(e)
Kronecker delta of size 3x3
sage: id.comp(f)
Kronecker delta of size 3x3
trace()

Return the trace of self.

OUTPUT:

  • element of the base ring of the module on which self is defined, equal to the trace of self.

EXAMPLES:

Trace of an automorphism on a \(\ZZ\)-module of rank 2:

sage: M = FiniteRankFreeModule(ZZ, 2, name='M')
sage: e = M.basis('e')
sage: a = M.automorphism([[4,7],[3,5]], name='a')
sage: a.matrix(e)
[4 7]
[3 5]
sage: a.trace()
9
sage: id = M.identity_map()
sage: id.trace()
2