Free modules of finite rank

The class FiniteRankFreeModule implements free modules of finite rank over a commutative ring.

A free module of finite rank over a commutative ring \(R\) is a module \(M\) over \(R\) that admits a finite basis, i.e. a finite familly of linearly independent generators. Since \(R\) is commutative, it has the invariant basis number property, so that the rank of the free module \(M\) is defined uniquely, as the cardinality of any basis of \(M\).

No distinguished basis of \(M\) is assumed. On the contrary, many bases can be introduced on the free module along with change-of-basis rules (as module automorphisms). Each module element has then various representations over the various bases.

Note

The class FiniteRankFreeModule does not inherit from class FreeModule_generic nor from class CombinatorialFreeModule, since both classes deal with modules with a distinguished basis (see details below). Accordingly, the class FiniteRankFreeModule inherits directly from the generic class Parent with the category set to Modules (and not to ModulesWithBasis).

Todo

  • implement submodules
  • create a FreeModules category (cf. the TODO statement in the documentation of Modules: Implement a ``FreeModules(R)`` category, when so prompted by a concrete use case)

AUTHORS:

  • Eric Gourgoulhon, Michal Bejger (2014-2015): initial version
  • Travis Scrimshaw (2016): category set to Modules(ring).FiniteDimensional() (trac ticket #20770)

REFERENCES:

EXAMPLES:

Let us define a free module of rank 2 over \(\ZZ\):

sage: M = FiniteRankFreeModule(ZZ, 2, name='M') ; M
Rank-2 free module M over the Integer Ring
sage: M.category()
Category of finite dimensional modules over Integer Ring

We introduce a first basis on M:

sage: e = M.basis('e') ; e
Basis (e_0,e_1) on the Rank-2 free module M over the Integer Ring

The elements of the basis are of course module elements:

sage: e[0]
Element e_0 of the Rank-2 free module M over the Integer Ring
sage: e[1]
Element e_1 of the Rank-2 free module M over the Integer Ring
sage: e[0].parent()
Rank-2 free module M over the Integer Ring

We define a module element by its components w.r.t. basis e:

sage: u = M([2,-3], basis=e, name='u')
sage: u.display(e)
u = 2 e_0 - 3 e_1

Module elements can be also be created by arithmetic expressions:

sage: v = -2*u + 4*e[0] ; v
Element of the Rank-2 free module M over the Integer Ring
sage: v.display(e)
6 e_1
sage: u == 2*e[0] - 3*e[1]
True

We define a second basis on M from a family of linearly independent elements:

sage: f = M.basis('f', from_family=(e[0]-e[1], -2*e[0]+3*e[1])) ; f
Basis (f_0,f_1) on the Rank-2 free module M over the Integer Ring
sage: f[0].display(e)
f_0 = e_0 - e_1
sage: f[1].display(e)
f_1 = -2 e_0 + 3 e_1

We may of course express the elements of basis e in terms of basis f:

sage: e[0].display(f)
e_0 = 3 f_0 + f_1
sage: e[1].display(f)
e_1 = 2 f_0 + f_1

as well as any module element:

sage: u.display(f)
u = -f_1
sage: v.display(f)
12 f_0 + 6 f_1

The two bases are related by a module automorphism:

sage: a = M.change_of_basis(e,f) ; a
Automorphism 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)
[ 1 -2]
[-1  3]

Let us check that basis f is indeed the image of basis e by a:

sage: f[0] == a(e[0])
True
sage: f[1] == a(e[1])
True

The reverse change of basis is of course the inverse automorphism:

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

We introduce a new module element via its components w.r.t. basis f:

sage: v = M([2,4], basis=f, name='v')
sage: v.display(f)
v = 2 f_0 + 4 f_1

The sum of the two module elements u and v can be performed even if they have been defined on different bases, thanks to the known relation between the two bases:

sage: s = u + v ; s
Element u+v of the Rank-2 free module M over the Integer Ring

We can display the result in either basis:

sage: s.display(e)
u+v = -4 e_0 + 7 e_1
sage: s.display(f)
u+v = 2 f_0 + 3 f_1

Tensor products of elements are implemented:

sage: t = u*v ; t
Type-(2,0) tensor u*v on the Rank-2 free module M over the Integer Ring
sage: t.parent()
Free module of type-(2,0) tensors on the
 Rank-2 free module M over the Integer Ring
sage: t.display(e)
u*v = -12 e_0*e_0 + 20 e_0*e_1 + 18 e_1*e_0 - 30 e_1*e_1
sage: t.display(f)
u*v = -2 f_1*f_0 - 4 f_1*f_1

We can access to tensor components w.r.t. to a given basis via the square bracket operator:

sage: t[e,0,1]
20
sage: t[f,1,0]
-2
sage: u[e,0]
2
sage: u[e,:]
[2, -3]
sage: u[f,:]
[0, -1]

The parent of the automorphism a is the group \(\mathrm{GL}(M)\), but a can also be considered as a tensor of type \((1,1)\) on M:

sage: a.parent()
General linear group of the Rank-2 free module M over the Integer Ring
sage: a.tensor_type()
(1, 1)
sage: a.display(e)
e_0*e^0 - 2 e_0*e^1 - e_1*e^0 + 3 e_1*e^1
sage: a.display(f)
f_0*f^0 - 2 f_0*f^1 - f_1*f^0 + 3 f_1*f^1

As such, we can form its tensor product with t, yielding a tensor of type \((3,1)\):

sage: t*a
Type-(3,1) tensor on the Rank-2 free module M over the Integer Ring
sage: (t*a).display(e)
-12 e_0*e_0*e_0*e^0 + 24 e_0*e_0*e_0*e^1 + 12 e_0*e_0*e_1*e^0
 - 36 e_0*e_0*e_1*e^1 + 20 e_0*e_1*e_0*e^0 - 40 e_0*e_1*e_0*e^1
 - 20 e_0*e_1*e_1*e^0 + 60 e_0*e_1*e_1*e^1 + 18 e_1*e_0*e_0*e^0
 - 36 e_1*e_0*e_0*e^1 - 18 e_1*e_0*e_1*e^0 + 54 e_1*e_0*e_1*e^1
 - 30 e_1*e_1*e_0*e^0 + 60 e_1*e_1*e_0*e^1 + 30 e_1*e_1*e_1*e^0
 - 90 e_1*e_1*e_1*e^1

The parent of \(t\otimes a\) is itself a free module of finite rank over \(\ZZ\):

sage: T = (t*a).parent() ; T
Free module of type-(3,1) tensors on the Rank-2 free module M over the
 Integer Ring
sage: T.base_ring()
Integer Ring
sage: T.rank()
16

Differences between FiniteRankFreeModule and FreeModule (or VectorSpace)

To illustrate the differences, let us create two free modules of rank 3 over \(\ZZ\), one with FiniteRankFreeModule and the other one with FreeModule:

sage: M = FiniteRankFreeModule(ZZ, 3, name='M') ; M
Rank-3 free module M over the Integer Ring
sage: N = FreeModule(ZZ, 3) ; N
Ambient free module of rank 3 over the principal ideal domain Integer Ring

The main difference is that FreeModule returns a free module with a distinguished basis, while FiniteRankFreeModule does not:

sage: N.basis()
[
(1, 0, 0),
(0, 1, 0),
(0, 0, 1)
]
sage: M.bases()
[]
sage: M.print_bases()
No basis has been defined on the Rank-3 free module M over the Integer Ring

This is also revealed by the category of each module:

sage: M.category()
Category of finite dimensional modules over Integer Ring
sage: N.category()
Category of finite dimensional modules with basis over
 (euclidean domains and infinite enumerated sets and metric spaces)

In other words, the module created by FreeModule is actually \(\ZZ^3\), while, in the absence of any distinguished basis, no canonical isomorphism relates the module created by FiniteRankFreeModule to \(\ZZ^3\):

sage: N is ZZ^3
True
sage: M is ZZ^3
False
sage: M == ZZ^3
False

Because it is \(\ZZ^3\), N is unique, while there may be various modules of the same rank over the same ring created by FiniteRankFreeModule; they are then distinguished by their names (actually by the complete sequence of arguments of FiniteRankFreeModule):

sage: N1 = FreeModule(ZZ, 3) ; N1
Ambient free module of rank 3 over the principal ideal domain Integer Ring
sage: N1 is N  # FreeModule(ZZ, 3) is unique
True
sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M_1') ; M1
Rank-3 free module M_1 over the Integer Ring
sage: M1 is M  # M1 and M are different rank-3 modules over ZZ
False
sage: M1b = FiniteRankFreeModule(ZZ, 3, name='M_1') ; M1b
Rank-3 free module M_1 over the Integer Ring
sage: M1b is M1  # because M1b and M1 have the same name
True

As illustrated above, various bases can be introduced on the module created by FiniteRankFreeModule:

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: f = M.basis('f', from_family=(-e[0], e[1]-e[2], -2*e[1]+3*e[2])) ; f
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring
sage: M.bases()
[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]

Each element of a basis is accessible via its index:

sage: e[0]
Element e_0 of the Rank-3 free module M over the Integer Ring
sage: e[0].parent()
Rank-3 free module M over the Integer Ring
sage: f[1]
Element f_1 of the Rank-3 free module M over the Integer Ring
sage: f[1].parent()
Rank-3 free module M over the Integer Ring

while on module N, the element of the (unique) basis is accessible directly from the module symbol:

sage: N.0
(1, 0, 0)
sage: N.1
(0, 1, 0)
sage: N.0.parent()
Ambient free module of rank 3 over the principal ideal domain Integer Ring

The arithmetic of elements is similar; the difference lies in the display: a basis has to be specified for elements of M, while elements of N are displayed directly as elements of \(\ZZ^3\):

sage: u = 2*e[0] - 3*e[2] ; u
Element of the Rank-3 free module M over the Integer Ring
sage: u.display(e)
2 e_0 - 3 e_2
sage: u.display(f)
-2 f_0 - 6 f_1 - 3 f_2
sage: u[e,:]
[2, 0, -3]
sage: u[f,:]
[-2, -6, -3]
sage: v = 2*N.0 - 3*N.2 ; v
(2, 0, -3)

For the case of M, in order to avoid to specify the basis if the user is always working with the same basis (e.g. only one basis has been defined), the concept of default basis has been introduced:

sage: M.default_basis()
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring
sage: M.print_bases()
Bases defined on the Rank-3 free module M over the Integer Ring:
 - (e_0,e_1,e_2) (default basis)
 - (f_0,f_1,f_2)

This is different from the distinguished basis of N: it simply means that the mention of the basis can be omitted in function arguments:

sage: u.display()  # equivalent to u.display(e)
2 e_0 - 3 e_2
sage: u[:]         # equivalent to u[e,:]
[2, 0, -3]

At any time, the default basis can be changed:

sage: M.set_default_basis(f)
sage: u.display()
-2 f_0 - 6 f_1 - 3 f_2

Another difference between FiniteRankFreeModule and FreeModule is that for the former the range of indices can be specified (by default, it starts from 0):

sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) ; M
Rank-3 free module M over the Integer Ring
sage: e = M.basis('e') ; e  # compare with (e_0,e_1,e_2) above
Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring
sage: e[1], e[2], e[3]
(Element e_1 of the Rank-3 free module M over the Integer Ring,
 Element e_2 of the Rank-3 free module M over the Integer Ring,
 Element e_3 of the Rank-3 free module M over the Integer Ring)

All the above holds for VectorSpace instead of FreeModule: the object created by VectorSpace is actually a Cartesian power of the base field:

sage: V = VectorSpace(QQ,3) ; V
Vector space of dimension 3 over Rational Field
sage: V.category()
Category of finite dimensional vector spaces with basis
 over (number fields and quotient fields and metric spaces)
sage: V is QQ^3
True
sage: V.basis()
[
(1, 0, 0),
(0, 1, 0),
(0, 0, 1)
]

To create a vector space without any distinguished basis, one has to use FiniteRankFreeModule:

sage: V = FiniteRankFreeModule(QQ, 3, name='V') ; V
3-dimensional vector space V over the Rational Field
sage: V.category()
Category of finite dimensional vector spaces over Rational Field
sage: V.bases()
[]
sage: V.print_bases()
No basis has been defined on the 3-dimensional vector space V over the
 Rational Field

The class FiniteRankFreeModule has been created for the needs of the SageManifolds project, where free modules do not have any distinguished basis. Too kinds of free modules occur in the context of differentiable manifolds (see here for more details):

  • the tangent vector space at any point of the manifold (cf. TangentSpace);
  • the set of vector fields on a parallelizable open subset \(U\) of the manifold, which is a free module over the algebra of scalar fields on \(U\) (cf. VectorFieldFreeModule).

For instance, without any specific coordinate choice, no basis can be distinguished in a tangent space.

On the other side, the modules created by FreeModule have much more algebraic functionalities than those created by FiniteRankFreeModule. In particular, submodules have not been implemented yet in FiniteRankFreeModule. Moreover, modules resulting from FreeModule are tailored to the specific kind of their base ring:

  • free module over a commutative ring that is not an integral domain (\(\ZZ/6\ZZ\)):

    sage: R = IntegerModRing(6) ; R
    Ring of integers modulo 6
    sage: FreeModule(R, 3)
    Ambient free module of rank 3 over Ring of integers modulo 6
    sage: type(FreeModule(R, 3))
    <class 'sage.modules.free_module.FreeModule_ambient_with_category'>
    
  • free module over an integral domain that is not principal (\(\ZZ[X]\)):

    sage: R.<X> = ZZ[] ; R
    Univariate Polynomial Ring in X over Integer Ring
    sage: FreeModule(R, 3)
    Ambient free module of rank 3 over the integral domain Univariate
     Polynomial Ring in X over Integer Ring
    sage: type(FreeModule(R, 3))
    <class 'sage.modules.free_module.FreeModule_ambient_domain_with_category'>
    
  • free module over a principal ideal domain (\(\ZZ\)):

    sage: R = ZZ ; R
    Integer Ring
    sage: FreeModule(R,3)
    Ambient free module of rank 3 over the principal ideal domain Integer Ring
    sage: type(FreeModule(R, 3))
    <class 'sage.modules.free_module.FreeModule_ambient_pid_with_category'>
    

On the contrary, all objects constructed with FiniteRankFreeModule belong to the same class:

sage: R = IntegerModRing(6)
sage: type(FiniteRankFreeModule(R, 3))
<class 'sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule_with_category'>
sage: R.<X> = ZZ[]
sage: type(FiniteRankFreeModule(R, 3))
<class 'sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule_with_category'>
sage: R = ZZ
sage: type(FiniteRankFreeModule(R, 3))
<class 'sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule_with_category'>

Differences between FiniteRankFreeModule and CombinatorialFreeModule

An alternative to construct free modules in Sage is CombinatorialFreeModule. However, as FreeModule, it leads to a module with a distinguished basis:

sage: N = CombinatorialFreeModule(ZZ, [1,2,3]) ; N
Free module generated by {1, 2, 3} over Integer Ring
sage: N.category()
Category of finite dimensional modules with basis over Integer Ring

The distinguished basis is returned by the method basis():

sage: b = N.basis() ; b
Finite family {1: B[1], 2: B[2], 3: B[3]}
sage: b[1]
B[1]
sage: b[1].parent()
Free module generated by {1, 2, 3} over Integer Ring

For the free module M created above with FiniteRankFreeModule, the method basis has at least one argument: the symbol string that specifies which basis is required:

sage: e = M.basis('e') ; e
Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring
sage: e[1]
Element e_1 of the Rank-3 free module M over the Integer Ring
sage: e[1].parent()
Rank-3 free module M over the Integer Ring

The arithmetic of elements is similar:

sage: u = 2*e[1] - 5*e[3] ; u
Element of the Rank-3 free module M over the Integer Ring
sage: v = 2*b[1] - 5*b[3] ; v
2*B[1] - 5*B[3]

One notices that elements of N are displayed directly in terms of their expansions on the distinguished basis. For elements of M, one has to use the method display() in order to specify the basis:

sage: u.display(e)
2 e_1 - 5 e_3

The components on the basis are returned by the square bracket operator for M and by the method coefficient for N:

sage: [u[e,i] for i in {1,2,3}]
[2, 0, -5]
sage: u[e,:]  # a shortcut for the above
[2, 0, -5]
sage: [v.coefficient(i) for i in {1,2,3}]
[2, 0, -5]
sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule

Free module of finite rank over a commutative ring.

A free module of finite rank over a commutative ring \(R\) is a module \(M\) over \(R\) that admits a finite basis, i.e. a finite familly of linearly independent generators. Since \(R\) is commutative, it has the invariant basis number property, so that the rank of the free module \(M\) is defined uniquely, as the cardinality of any basis of \(M\).

No distinguished basis of \(M\) is assumed. On the contrary, many bases can be introduced on the free module along with change-of-basis rules (as module automorphisms). Each module element has then various representations over the various bases.

Note

The class FiniteRankFreeModule does not inherit from class FreeModule_generic nor from class CombinatorialFreeModule, since both classes deal with modules with a distinguished basis (see details above). Moreover, following the recommendation exposed in trac ticket #16427 the class FiniteRankFreeModule inherits directly from Parent (with the category set to Modules) and not from the Cython class Module.

The class FiniteRankFreeModule is a Sage parent class, the corresponding element class being FiniteRankFreeModuleElement.

INPUT:

  • ring – commutative ring \(R\) over which the free module is constructed
  • rank – positive integer; rank of the free module
  • name – (default: None) string; name given to the free module
  • latex_name – (default: None) string; LaTeX symbol to denote the freemodule; if none is provided, it is set to name
  • start_index – (default: 0) integer; lower bound of the range of indices in bases defined on the free module
  • output_formatter – (default: None) function or unbound method called to format the output of the tensor components; output_formatter must take 1 or 2 arguments: the first argument must be an element of the ring \(R\) and the second one, if any, some format specification

EXAMPLES:

Free module of rank 3 over \(\ZZ\):

sage: FiniteRankFreeModule._clear_cache_() # for doctests only
sage: M = FiniteRankFreeModule(ZZ, 3) ; M
Rank-3 free module over the Integer Ring
sage: M = FiniteRankFreeModule(ZZ, 3, name='M') ; M  # declaration with a name
Rank-3 free module M over the Integer Ring
sage: M.category()
Category of finite dimensional modules over Integer Ring
sage: M.base_ring()
Integer Ring
sage: M.rank()
3

If the base ring is a field, the free module is in the category of vector spaces:

sage: V = FiniteRankFreeModule(QQ, 3, name='V') ; V
3-dimensional vector space V over the Rational Field
sage: V.category()
Category of finite dimensional vector spaces over Rational Field

The LaTeX output is adjusted via the parameter latex_name:

sage: latex(M)  # the default is the symbol provided in the string ``name``
M
sage: M = FiniteRankFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}')
sage: latex(M)
\mathcal{M}

The free module M has no distinguished basis:

sage: M in ModulesWithBasis(ZZ)
False
sage: M in Modules(ZZ)
True

In particular, no basis is initialized at the module construction:

sage: M.print_bases()
No basis has been defined on the Rank-3 free module M over the Integer Ring
sage: M.bases()
[]

Bases have to be introduced by means of the method basis(), the first defined basis being considered as the default basis, meaning it can be skipped in function arguments required a basis (this can be changed by means of the method set_default_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: M.default_basis()
Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring

A second basis can be created from a family of linearly independent elements expressed in terms of basis e:

sage: f = M.basis('f', from_family=(-e[0], e[1]+e[2], 2*e[1]+3*e[2]))
sage: f
Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring
sage: M.print_bases()
Bases defined on the Rank-3 free module M over the Integer Ring:
 - (e_0,e_1,e_2) (default basis)
 - (f_0,f_1,f_2)
sage: M.bases()
[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]

M is a parent object, whose elements are instances of FiniteRankFreeModuleElement (actually a dynamically generated subclass of it):

sage: v = M.an_element() ; v
Element of the Rank-3 free module M over the Integer Ring
sage: from sage.tensor.modules.free_module_element import FiniteRankFreeModuleElement
sage: isinstance(v, FiniteRankFreeModuleElement)
True
sage: v in M
True
sage: M.is_parent_of(v)
True
sage: v.display() # expansion w.r.t. the default basis (e)
e_0 + e_1 + e_2
sage: v.display(f)
-f_0 + f_1

The test suite of the category of modules is passed:

sage: TestSuite(M).run()

Constructing an element of M from (the integer) 0 yields the zero element of M:

sage: M(0)
Element zero of the Rank-3 free module M over the Integer Ring
sage: M(0) is M.zero()
True

Non-zero elements are constructed by providing their components in a given basis:

sage: v = M([-1,0,3]) ; v  # components in the default basis (e)
Element of the Rank-3 free module M over the Integer Ring
sage: v.display() # expansion w.r.t. the default basis (e)
-e_0 + 3 e_2
sage: v.display(f)
f_0 - 6 f_1 + 3 f_2
sage: v = M([-1,0,3], basis=f) ; v  # components in a specific basis
Element of the Rank-3 free module M over the Integer Ring
sage: v.display(f)
-f_0 + 3 f_2
sage: v.display()
e_0 + 6 e_1 + 9 e_2
sage: v = M([-1,0,3], basis=f, name='v') ; v
Element v of the Rank-3 free module M over the Integer Ring
sage: v.display(f)
v = -f_0 + 3 f_2
sage: v.display()
v = e_0 + 6 e_1 + 9 e_2

An alternative is to construct the element from an empty list of componentsand to set the nonzero components afterwards:

sage: v = M([], name='v')
sage: v[e,0] = -1
sage: v[e,2] = 3
sage: v.display(e)
v = -e_0 + 3 e_2

Indices on the free module, such as indices labelling the element of a basis, are provided by the generator method irange(). By default, they range from 0 to the module’s rank minus one:

sage: list(M.irange())
[0, 1, 2]

This can be changed via the parameter start_index in the module construction:

sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1)
sage: list(M1.irange())
[1, 2, 3]

The parameter output_formatter in the constructor of the free module is used to set the output format of tensor components:

sage: N = FiniteRankFreeModule(QQ, 3, output_formatter=Rational.numerical_approx)
sage: e = N.basis('e')
sage: v = N([1/3, 0, -2], basis=e)
sage: v[e,:]
[0.333333333333333, 0.000000000000000, -2.00000000000000]
sage: v.display(e)  # default format (53 bits of precision)
0.333333333333333 e_0 - 2.00000000000000 e_2
sage: v.display(e, format_spec=10)  # 10 bits of precision
0.33 e_0 - 2.0 e_2