Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/modules/free_module_morphism.py
4036 views
1
"""
2
Morphisms of free modules
3
4
AUTHORS:
5
- William Stein: initial version
6
7
- Miguel Marco (2010-06-19): added eigenvalues, eigenvectors and minpoly functions
8
9
10
TESTS::
11
12
sage: V = ZZ^2; f = V.hom([V.1,-2*V.0])
13
sage: loads(dumps(f))
14
Free module morphism defined by the matrix
15
[ 0 1]
16
[-2 0]
17
Domain: Ambient free module of rank 2 over the principal ideal domain ...
18
Codomain: Ambient free module of rank 2 over the principal ideal domain ...
19
sage: loads(dumps(f)) == f
20
True
21
"""
22
23
####################################################################################
24
# Copyright (C) 2009 William Stein <[email protected]>
25
#
26
# Distributed under the terms of the GNU General Public License (GPL)
27
#
28
# This code is distributed in the hope that it will be useful,
29
# but WITHOUT ANY WARRANTY; without even the implied warranty of
30
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31
# General Public License for more details.
32
#
33
# The full text of the GPL is available at:
34
#
35
# http://www.gnu.org/licenses/
36
####################################################################################
37
38
# A matrix morphism is a morphism that is defined by multiplication by a
39
# matrix. Elements of domain must either have a method "vector()" that
40
# returns a vector that the defining matrix can hit from the left, or
41
# be coercible into vector space of appropriate dimension.
42
43
import sage.misc.misc as misc
44
import sage.modules.free_module as free_module
45
import matrix_morphism
46
from sage.structure.sequence import Sequence
47
48
import free_module_homspace
49
50
def is_FreeModuleMorphism(x):
51
"""
52
EXAMPLES::
53
54
sage: V = ZZ^2; f = V.hom([V.1,-2*V.0])
55
sage: sage.modules.free_module_morphism.is_FreeModuleMorphism(f)
56
True
57
sage: sage.modules.free_module_morphism.is_FreeModuleMorphism(0)
58
False
59
"""
60
return isinstance(x, FreeModuleMorphism)
61
62
class FreeModuleMorphism(matrix_morphism.MatrixMorphism):
63
def __init__(self, parent, A):
64
"""
65
INPUT:
66
67
- ``parent`` - a homspace in a (sub) category of free modules
68
69
- ``A`` - matrix
70
71
EXAMPLES::
72
73
sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ)
74
sage: phi = V.hom(matrix(ZZ,3,[1..9]))
75
sage: type(phi)
76
<class 'sage.modules.free_module_morphism.FreeModuleMorphism'>
77
"""
78
if not free_module_homspace.is_FreeModuleHomspace(parent):
79
raise TypeError, "parent (=%s) must be a free module hom space"%parent
80
if isinstance(A, matrix_morphism.MatrixMorphism):
81
A = A.matrix()
82
A = parent._matrix_space()(A)
83
matrix_morphism.MatrixMorphism.__init__(self, parent, A)
84
85
def __call__(self, x):
86
"""
87
Evaluate this matrix morphism at x, which is either an element
88
that can be coerced into the domain or a submodule of the domain.
89
90
EXAMPLES::
91
92
sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ)
93
sage: phi = V.hom(matrix(QQ,3,[1..9]))
94
95
We compute the image of some elements::
96
97
sage: phi(V.0)
98
(1, 2, 3)
99
sage: phi(V.1)
100
(4, 5, 6)
101
sage: phi(V.0 - 1/4*V.1)
102
(0, 3/4, 3/2)
103
104
We compute the image of a *subspace*::
105
106
sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ)
107
sage: phi = V.hom(matrix(QQ,3,[1..9]))
108
sage: phi.rank()
109
2
110
sage: phi(V)
111
Vector space of degree 3 and dimension 2 over Rational Field
112
Basis matrix:
113
[ 1 0 -1]
114
[ 0 1 2]
115
116
We restrict phi to W and compute the image of an element::
117
118
sage: psi = phi.restrict_domain(W)
119
sage: psi(W.0) == phi(W.0)
120
True
121
sage: psi(W.1) == phi(W.1)
122
True
123
124
We compute the image of a submodule of a ZZ-module embedded in
125
a rational vector space::
126
127
sage: V = QQ^3; W = V.span_of_basis([[2,2,3],[-1,2,5/3]], ZZ)
128
sage: phi = W.hom([W.0, W.0-W.1]); phi
129
Free module morphism defined by the matrix
130
[ 1 0]
131
[ 1 -1]...
132
sage: phi(span([2*W.1],ZZ))
133
Free module of degree 3 and rank 1 over Integer Ring
134
Echelon basis matrix:
135
[ 6 0 8/3]
136
sage: phi(2*W.1)
137
(6, 0, 8/3)
138
"""
139
if free_module.is_FreeModule(x):
140
V = self.domain().submodule(x)
141
return self.restrict_domain(V).image()
142
return matrix_morphism.MatrixMorphism.__call__(self, x)
143
144
def _repr_(self):
145
r"""
146
Return string representation of this morphism of free modules.
147
148
EXAMPLES::
149
150
sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ)
151
sage: phi = V.hom(matrix(ZZ,3,[1..9]))
152
sage: phi._repr_()
153
'Free module morphism defined by the matrix\n[1 2 3]\n[4 5 6]\n[7 8 9]\nDomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring\nCodomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring'
154
155
sage: V = ZZ^6
156
sage: W = ZZ^4
157
sage: m = matrix(QQ, [[1, 0, 0 ,0], [0]*4, [0]*4, [0]*4, [0]*4, [0]*4])
158
sage: phi = V.hom(m, W)
159
sage: rho = phi.restrict_codomain(W.span([W.0]))
160
sage: rho
161
Free module morphism defined by the matrix
162
[1]
163
[0]
164
[0]
165
[0]
166
[0]
167
[0]
168
Domain: Ambient free module of rank 6 over the principal ideal domain Integer Ring
169
Codomain: Free module of degree 4 and rank 1 over Integer Ring
170
Echelon basis matrix:
171
[1 0 0 0]
172
173
sage: V = QQ^40
174
sage: m = matrix(QQ, 40, 40, 1600)
175
sage: phi = V.hom(m, V)
176
sage: phi
177
Vector space morphism represented by the matrix:
178
40 x 40 dense matrix over Rational Field
179
Domain: Vector space of dimension 40 over Rational Field
180
Codomain: Vector space of dimension 40 over Rational Field
181
"""
182
r = "Free module morphism defined by the matrix\n{0}\nDomain: {1}\nCodomain: {2}"
183
return r.format(self.matrix(), self.domain(), self.codomain())
184
185
def change_ring(self, R):
186
"""
187
Change the ring over which this morphism is defined. This changes the ring of the
188
domain, codomain, and underlying matrix.
189
190
EXAMPLES::
191
192
sage: V0 = span([[0,0,1],[0,2,0]],ZZ); V1 = span([[1/2,0],[0,2]],ZZ); W = span([[1,0],[0,6]],ZZ)
193
sage: h = V0.hom([-3*V1.0-3*V1.1, -3*V1.0-3*V1.1])
194
sage: h.base_ring()
195
Integer Ring
196
sage: h
197
Free module morphism defined by the matrix
198
[-3 -3]
199
[-3 -3]...
200
sage: h.change_ring(QQ).base_ring()
201
Rational Field
202
sage: f = h.change_ring(QQ); f
203
Vector space morphism represented by the matrix:
204
[-3 -3]
205
[-3 -3]
206
Domain: Vector space of degree 3 and dimension 2 over Rational Field
207
Basis matrix:
208
[0 1 0]
209
[0 0 1]
210
Codomain: Vector space of degree 2 and dimension 2 over Rational Field
211
Basis matrix:
212
[1 0]
213
[0 1]
214
sage: f = h.change_ring(GF(7)); f
215
Vector space morphism represented by the matrix:
216
[4 4]
217
[4 4]
218
Domain: Vector space of degree 3 and dimension 2 over Finite Field of size 7
219
Basis matrix:
220
[0 1 0]
221
[0 0 1]
222
Codomain: Vector space of degree 2 and dimension 2 over Finite Field of size 7
223
Basis matrix:
224
[1 0]
225
[0 1]
226
"""
227
D = self.domain().change_ring(R)
228
C = self.codomain().change_ring(R)
229
A = self.matrix().change_ring(R)
230
return D.hom(A, C)
231
232
def inverse_image(self, V):
233
"""
234
Given a submodule V of the codomain of self, return the
235
inverse image of V under self, i.e., the biggest submodule of
236
the domain of self that maps into V.
237
238
EXAMPLES:
239
240
We test computing inverse images over a field::
241
242
sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ)
243
sage: phi = V.hom(matrix(QQ,3,[1..9]))
244
sage: phi.rank()
245
2
246
sage: I = phi.inverse_image(W); I
247
Vector space of degree 3 and dimension 2 over Rational Field
248
Basis matrix:
249
[ 1 0 0]
250
[ 0 1 -1/2]
251
sage: phi(I.0) in W
252
True
253
sage: phi(I.1) in W
254
True
255
sage: W = phi.image()
256
sage: phi.inverse_image(W) == V
257
True
258
259
We test computing inverse images between two spaces embedded in different
260
ambient spaces.::
261
262
sage: V0 = span([[0,0,1],[0,2,0]],ZZ); V1 = span([[1/2,0],[0,2]],ZZ); W = span([[1,0],[0,6]],ZZ)
263
sage: h = V0.hom([-3*V1.0-3*V1.1, -3*V1.0-3*V1.1])
264
sage: h.inverse_image(W)
265
Free module of degree 3 and rank 2 over Integer Ring
266
Echelon basis matrix:
267
[0 2 1]
268
[0 0 2]
269
sage: h(h.inverse_image(W)).is_submodule(W)
270
True
271
sage: h(h.inverse_image(W)).index_in(W)
272
+Infinity
273
sage: h(h.inverse_image(W))
274
Free module of degree 2 and rank 1 over Integer Ring
275
Echelon basis matrix:
276
[ 3 12]
277
278
279
We test computing inverse images over the integers::
280
281
sage: V = QQ^3; W = V.span_of_basis([[2,2,3],[-1,2,5/3]], ZZ)
282
sage: phi = W.hom([W.0, W.0-W.1])
283
sage: Z = W.span([2*W.1]); Z
284
Free module of degree 3 and rank 1 over Integer Ring
285
Echelon basis matrix:
286
[ 2 -4 -10/3]
287
sage: Y = phi.inverse_image(Z); Y
288
Free module of degree 3 and rank 1 over Integer Ring
289
Echelon basis matrix:
290
[ 6 0 8/3]
291
sage: phi(Y) == Z
292
True
293
"""
294
if self.rank() == 0:
295
# Special case -- if this is the 0 map, then the only possibility
296
# for the inverse image is that it is the whole domain.
297
return self.domain()
298
299
R = self.base_ring()
300
A = self.matrix()
301
302
# Replace the module V that we are going to pullback by a
303
# submodule that is contained in the image of self, since our
304
# plan is to lift all generators of V.
305
V = self.image().intersection(V)
306
# Write V in terms of the basis for the codomain.
307
V = self.codomain().coordinate_module(V)
308
B = V.basis_matrix()
309
310
# Compute the kernel, which is contained in the inverse image.
311
K = self.kernel()
312
313
if R.is_field():
314
# By solving, find lifts of each of the basis elements of V.
315
# Each row of C gives a linear combination of the basis for the domain
316
# that maps to one of the basis elements V.
317
C = A.solve_left(B)
318
319
else:
320
if not hasattr(A, 'hermite_form'):
321
raise NotImplementedError, "base ring (%s) must have hermite_form algorithm in order to compute inverse image"%R
322
323
# 1. Compute H such that U*A = H = hnf(A) without zero
324
# rows. What this "does" is find a basis for the image of
325
# A and explicitly represents each element in this basis
326
# as the image of some element of the domain (the rows of
327
# U give these elements of the domain).
328
H, U = A.hermite_form(transformation=True,include_zero_rows=False)
329
330
# 2. Next we find the unique solution to the equation
331
# Y*H = B. This writes each basis element of V in
332
# terms of our image basis found in the previous step.
333
Y = H.solve_left(B)
334
335
# 3. Multiply Y by U then takes those same linear combinations
336
# from step 2 above and lifts them to coefficients that define
337
# linear combinations of the basis for the domain.
338
C = Y*U
339
340
# Finally take the linear combinations of the basis for the
341
# domain defined by C. Together with the kernel K, this spans
342
# the inverse image of V.
343
if self.domain().is_ambient():
344
L = C.row_module(R)
345
else:
346
L = (C*self.domain().basis_matrix()).row_module(R)
347
348
return K + L
349
350
def lift(self, x):
351
r"""
352
Given an element of the image, return an element of the codomain that maps onto it.
353
354
Note that ``lift`` and ``preimage_representative`` are
355
equivalent names for this method, with the latter suggesting
356
that the return value is a coset representative of the domain
357
modulo the kernel of the morphism.
358
359
EXAMPLE::
360
361
sage: X = QQ**2
362
sage: V = X.span([[2, 0], [0, 8]], ZZ)
363
sage: W = (QQ**1).span([[1/12]], ZZ)
364
sage: f = V.hom([W([1/3]), W([1/2])], W)
365
sage: f.lift([1/3])
366
(8, -16)
367
sage: f.lift([1/2])
368
(12, -24)
369
sage: f.lift([1/6])
370
(4, -8)
371
sage: f.lift([1/12])
372
Traceback (most recent call last):
373
...
374
ValueError: element is not in the image
375
sage: f.lift([1/24])
376
Traceback (most recent call last):
377
...
378
TypeError: element (= [1/24]) is not in free module
379
380
This works for vector spaces, too::
381
382
sage: V = VectorSpace(GF(3), 2)
383
sage: W = VectorSpace(GF(3), 3)
384
sage: f = V.hom([W.1, W.1 - W.0])
385
sage: f.lift(W.1)
386
(1, 0)
387
sage: f.lift(W.2)
388
Traceback (most recent call last):
389
...
390
ValueError: element is not in the image
391
sage: w = W((17, -2, 0))
392
sage: f(f.lift(w)) == w
393
True
394
395
This example illustrates the use of the ``preimage_representative``
396
as an equivalent name for this method. ::
397
398
sage: V = ZZ^3
399
sage: W = ZZ^2
400
sage: w = vector(ZZ, [1,2])
401
sage: f = V.hom([w, w, w], W)
402
sage: f.preimage_representative(vector(ZZ, [10, 20]))
403
(0, 0, 10)
404
"""
405
from free_module_element import vector
406
x = self.codomain()(x)
407
A = self.matrix()
408
R = self.base_ring()
409
if R.is_field():
410
try:
411
C = A.solve_left(x)
412
except ValueError:
413
raise ValueError, "element is not in the image"
414
else:
415
# see inverse_image for similar code but with comments
416
if not hasattr(A, 'hermite_form'):
417
raise NotImplementedError, "base ring (%s) must have hermite_form algorithm in order to compute inverse image"%R
418
H, U = A.hermite_form(transformation=True,include_zero_rows=False)
419
Y = H.solve_left(vector(self.codomain().coordinates(x)))
420
C = Y*U
421
try:
422
t = self.domain().linear_combination_of_basis(C)
423
except TypeError:
424
raise ValueError, "element is not in the image"
425
assert self(t) == x
426
return t
427
428
preimage_representative = lift
429
430
def eigenvalues(self,extend=True):
431
r"""
432
Returns a list with the eigenvalues of the endomorphism of vector spaces.
433
434
INPUT:
435
436
- ``extend`` -- boolean (default: True) decides if base field
437
extensions should be considered or not.
438
439
EXAMPLES:
440
441
We compute the eigenvalues of an endomorphism of `\QQ^3`::
442
443
sage: V=QQ^3
444
sage: H=V.endomorphism_ring()([[1,-1,0],[-1,1,1],[0,3,1]])
445
sage: H.eigenvalues()
446
[3, 1, -1]
447
448
Note the effect of the ``extend`` option::
449
450
sage: V=QQ^2
451
sage: H=V.endomorphism_ring()([[0,-1],[1,0]])
452
sage: H.eigenvalues()
453
[-1*I, 1*I]
454
sage: H.eigenvalues(extend=False)
455
[]
456
"""
457
if self.base_ring().is_field():
458
if self.is_endomorphism():
459
return self.matrix().eigenvalues(extend=extend)
460
else:
461
raise TypeError, "not an endomorphism"
462
else:
463
raise NotImplementedError, "module must be a vector space"
464
465
def eigenvectors(self,extend=True):
466
"""
467
Computes the subspace of eigenvectors of a given eigenvalue.
468
469
INPUT:
470
471
- ``extend`` -- boolean (default: True) decides if base field
472
extensions should be considered or not.
473
474
OUTPUT:
475
476
A sequence of tuples. Each tuple contains an eigenvalue, a sequence
477
with a basis of the corresponding subspace of eigenvectors, and the
478
algebraic multiplicity of the eigenvalue.
479
480
EXAMPLES::
481
482
sage: V=(QQ^4).subspace([[0,2,1,4],[1,2,5,0],[1,1,1,1]])
483
sage: H=(V.Hom(V))(matrix(QQ, [[0,1,0],[-1,0,0],[0,0,3]]))
484
sage: H.eigenvectors()
485
[(3, [
486
(0, 0, 1, -6/7)
487
], 1), (-1*I, [
488
(1, 1*I, 0, -0.571428571428572? + 2.428571428571429?*I)
489
], 1), (1*I, [
490
(1, -1*I, 0, -0.571428571428572? - 2.428571428571429?*I)
491
], 1)]
492
sage: H.eigenvectors(extend=False)
493
[(3, [
494
(0, 0, 1, -6/7)
495
], 1)]
496
sage: H1=(V.Hom(V))(matrix(QQ, [[2,1,0],[0,2,0],[0,0,3]]))
497
sage: H1.eigenvectors()
498
[(3, [
499
(0, 0, 1, -6/7)
500
], 1), (2, [
501
(0, 1, 0, 17/7)
502
], 2)]
503
sage: H1.eigenvectors(extend=False)
504
[(3, [
505
(0, 0, 1, -6/7)
506
], 1), (2, [
507
(0, 1, 0, 17/7)
508
], 2)]
509
"""
510
if self.base_ring().is_field():
511
if self.is_endomorphism():
512
seigenvec=self.matrix().eigenvectors_left(extend=extend)
513
resu=[]
514
for i in seigenvec:
515
V=self.domain().base_extend(i[0].parent())
516
svectors=Sequence(map(lambda j: V(j * V.basis_matrix()),i[1]), cr=True)
517
resu.append((i[0],svectors,i[2]))
518
return resu
519
else:
520
raise TypeError, "not an endomorphism"
521
else:
522
raise NotImplementedError, "module must be a vector space"
523
524
def minimal_polynomial(self,var='x'):
525
r"""
526
Computes the minimal polynomial.
527
528
``minpoly()`` and ``minimal_polynomial()`` are the same method.
529
530
INPUT:
531
532
- ``var`` - string (default: 'x') a variable name
533
534
OUTPUT:
535
536
polynomial in var - the minimal polynomial of the endomorphism.
537
538
EXAMPLES:
539
540
Compute the minimal polynomial, and check it. ::
541
542
sage: V=GF(7)^3
543
sage: H=V.Hom(V)([[0,1,2],[-1,0,3],[2,4,1]])
544
sage: H
545
Vector space morphism represented by the matrix:
546
[0 1 2]
547
[6 0 3]
548
[2 4 1]
549
Domain: Vector space of dimension 3 over Finite Field of size 7
550
Codomain: Vector space of dimension 3 over Finite Field of size 7
551
552
sage: H.minpoly()
553
x^3 + 6*x^2 + 6*x + 1
554
555
sage: H.minimal_polynomial()
556
x^3 + 6*x^2 + 6*x + 1
557
558
sage: H^3 + (H^2)*6 + H*6 + 1
559
Vector space morphism represented by the matrix:
560
[0 0 0]
561
[0 0 0]
562
[0 0 0]
563
Domain: Vector space of dimension 3 over Finite Field of size 7
564
Codomain: Vector space of dimension 3 over Finite Field of size 7
565
"""
566
if self.is_endomorphism():
567
return self.matrix().minpoly(var)
568
else:
569
raise TypeError, "not an endomorphism"
570
571
minpoly = minimal_polynomial
572
573