Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/modules/fg_pid/fgp_morphism.py
4057 views
1
r"""
2
Morphisms between finitely generated modules over a PID
3
4
AUTHOR:
5
- William Stein, 2009
6
"""
7
8
####################################################################################
9
# Copyright (C) 2009 William Stein <[email protected]>
10
#
11
# Distributed under the terms of the GNU General Public License (GPL)
12
#
13
# This code is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
# General Public License for more details.
17
#
18
# The full text of the GPL is available at:
19
#
20
# http://www.gnu.org/licenses/
21
####################################################################################
22
23
from sage.categories.all import Morphism, is_Morphism
24
import fgp_module
25
26
27
class FGP_Morphism(Morphism):
28
"""
29
A morphism between finitely generated modules over a PID.
30
31
EXAMPLES:
32
33
An endomorphism::
34
35
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
36
sage: Q = V/W; Q
37
Finitely generated module V/W over Integer Ring with invariants (4, 12)
38
sage: phi = Q.hom([Q.0+3*Q.1, -Q.1]); phi
39
Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(1, 3), (0, 11)]
40
sage: phi(Q.0) == Q.0 + 3*Q.1
41
True
42
sage: phi(Q.1) == -Q.1
43
True
44
45
A morphism between different modules V1/W1 ---> V2/W2 in
46
different ambient spaces::
47
48
sage: V1 = ZZ^2; W1 = V1.span([[1,2],[3,4]]); A1 = V1/W1
49
sage: V2 = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W2 = V2.span([2*V2.0+4*V2.1, 9*V2.0+12*V2.1, 4*V2.2]); A2=V2/W2
50
sage: A1
51
Finitely generated module V/W over Integer Ring with invariants (2)
52
sage: A2
53
Finitely generated module V/W over Integer Ring with invariants (4, 12)
54
sage: phi = A1.hom([2*A2.0])
55
sage: phi(A1.0)
56
(2, 0)
57
sage: 2*A2.0
58
(2, 0)
59
sage: phi(2*A1.0)
60
(0, 0)
61
62
TESTS::
63
64
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W
65
sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1])
66
sage: loads(dumps(phi)) == phi
67
True
68
"""
69
def __init__(self, parent, phi, check=True):
70
"""
71
A morphism between finitely generated modules over a PID.
72
73
EXAMPLES::
74
75
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
76
sage: Q = V/W; Q
77
Finitely generated module V/W over Integer Ring with invariants (4, 12)
78
sage: phi = Q.hom([Q.0+3*Q.1, -Q.1]); phi
79
Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(1, 3), (0, 11)]
80
sage: phi(Q.0) == Q.0 + 3*Q.1
81
True
82
sage: phi(Q.1) == -Q.1
83
True
84
85
For full documentation, see :class:`FGP_Morphism`.
86
"""
87
Morphism.__init__(self, parent)
88
M = parent.domain()
89
N = parent.codomain()
90
if isinstance(phi, FGP_Morphism):
91
if check:
92
if phi.parent() != parent:
93
raise TypeError
94
phi = phi._phi
95
check = False # no need
96
97
# input: phi is a morphism from MO = M.optimized().V() to N.V()
98
# that sends MO.W() to N.W()
99
if check:
100
if not is_Morphism(phi) and M == N:
101
A = M.optimized()[0].V()
102
B = N.V()
103
s = M.base_ring()(phi) * B.coordinate_module(A).basis_matrix()
104
phi = A.Hom(B)(s)
105
106
MO, _ = M.optimized()
107
if phi.domain() != MO.V():
108
raise ValueError, "domain of phi must be the covering module for the optimized covering module of the domain"
109
if phi.codomain() != N.V():
110
raise ValueError, "codomain of phi must be the covering module the codomain."
111
# check that MO.W() gets sent into N.W()
112
# todo (optimize): this is slow:
113
for x in MO.W().basis():
114
if phi(x) not in N.W():
115
raise ValueError, "phi must send optimized submodule of M.W() into N.W()"
116
self._phi = phi
117
118
def _repr_(self):
119
"""
120
EXAMPLES::
121
122
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
123
sage: Q = V/W; Q
124
Finitely generated module V/W over Integer Ring with invariants (4, 12)
125
sage: phi = Q.hom([Q.0+3*Q.1, -Q.1])
126
sage: phi._repr_()
127
'Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(1, 3), (0, 11)]'
128
"""
129
return "Morphism from module over %s with invariants %s to module with invariants %s that sends the generators to %s"%(
130
self.domain().base_ring(), self.domain().invariants(), self.codomain().invariants(),
131
list(self.im_gens()))
132
133
def im_gens(self):
134
"""
135
Return tuple of the images of the generators of the domain
136
under this morphism.
137
138
EXAMPLES::
139
140
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W
141
sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1])
142
sage: phi.im_gens()
143
((1, 0), (1, 2))
144
sage: phi.im_gens() is phi.im_gens()
145
True
146
"""
147
try: return self.__im_gens
148
except AttributeError: pass
149
self.__im_gens = tuple([self(x) for x in self.domain().gens()])
150
return self.__im_gens
151
152
def __cmp__(self, right):
153
"""
154
EXAMPLES::
155
156
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W
157
sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1])
158
sage: phi.im_gens()
159
((1, 0), (1, 2))
160
sage: phi.im_gens() is phi.im_gens()
161
True
162
sage: phi == phi
163
True
164
sage: psi = Q.hom([Q.0,Q.0 - 2*Q.1])
165
sage: phi == psi
166
False
167
sage: psi = Q.hom([Q.0,Q.0 - 2*Q.1])
168
sage: cmp(phi,psi)
169
-1
170
sage: cmp(psi,phi)
171
1
172
sage: psi = Q.hom([Q.0,Q.0 + 2*Q.1])
173
sage: phi == psi
174
True
175
"""
176
if not isinstance(right, FGP_Morphism):
177
raise TypeError
178
a = (self.domain(), self.codomain())
179
b = (right.domain(), right.codomain())
180
c = cmp(a,b)
181
if c: return c
182
return cmp(self.im_gens(), right.im_gens())
183
184
def __add__(self, right):
185
"""
186
EXAMPLES::
187
188
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
189
sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1]); phi
190
Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(2, 0), (0, 1)]
191
sage: phi + phi
192
Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(0, 0), (0, 2)]
193
"""
194
if not isinstance(right, FGP_Morphism): # todo: implement using coercion model
195
right = self.parent()(right)
196
return FGP_Morphism(self.parent(), self._phi + right._phi, check=fgp_module.DEBUG)
197
198
def __sub__(self, right):
199
"""
200
EXAMPLES::
201
202
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
203
sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1])
204
sage: phi - phi
205
Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(0, 0), (0, 0)]
206
"""
207
if not isinstance(right, FGP_Morphism): # todo: implement using coercion model
208
right = self.parent()(right)
209
return FGP_Morphism(self.parent(), self._phi - right._phi, check=fgp_module.DEBUG)
210
211
def __neg__(self):
212
"""
213
EXAMPLES::
214
215
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
216
sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1])
217
sage: -phi
218
Morphism from module over Integer Ring with invariants (4, 12) to module with invariants (4, 12) that sends the generators to [(2, 0), (0, 11)]
219
"""
220
return FGP_Morphism(self.parent(), self._phi.__neg__(), check=fgp_module.DEBUG)
221
222
def __call__(self, x):
223
"""
224
EXAMPLES::
225
226
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
227
sage: Q = V/W
228
sage: phi = Q.hom([Q.0+3*Q.1, -Q.1]);
229
sage: phi(Q.0) == Q.0 + 3*Q.1
230
True
231
232
We compute the image of some submodules of the domain::
233
234
sage: phi(Q)
235
Finitely generated module V/W over Integer Ring with invariants (4, 12)
236
sage: phi(Q.submodule([Q.0]))
237
Finitely generated module V/W over Integer Ring with invariants (4)
238
sage: phi(Q.submodule([Q.1]))
239
Finitely generated module V/W over Integer Ring with invariants (12)
240
sage: phi(W/W)
241
Finitely generated module V/W over Integer Ring with invariants ()
242
243
We try to evaluate on a module that is not a submodule of the domain, which raises a ValueError::
244
245
sage: phi(V/W.scale(2))
246
Traceback (most recent call last):
247
...
248
ValueError: x must be a submodule or element of the domain
249
250
We evaluate on an element of the domain that is not in the V
251
for the optimized representation of the domain::
252
253
sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
254
sage: Q = V/W; Q
255
Finitely generated module V/W over Integer Ring with invariants (4, 12)
256
sage: O, X = Q.optimized()
257
sage: O.V()
258
Free module of degree 3 and rank 2 over Integer Ring
259
User basis matrix:
260
[0 0 1]
261
[0 2 0]
262
sage: phi = Q.hom([Q.0, 4*Q.1])
263
sage: x = Q(V.0); x
264
(0, 4)
265
sage: x == 4*Q.1
266
True
267
sage: x in O.V()
268
False
269
sage: phi(x)
270
(0, 4)
271
sage: phi(4*Q.1)
272
(0, 4)
273
sage: phi(4*Q.1) == phi(x)
274
True
275
"""
276
from fgp_module import is_FGP_Module
277
if is_FGP_Module(x):
278
if not x.is_submodule(self.domain()):
279
raise ValueError, "x must be a submodule or element of the domain"
280
# perhaps can be optimized with a matrix multiply; but note
281
# the subtlety of optimized representations.
282
return self.codomain().submodule([self(y) for y in x.smith_form_gens()])
283
else:
284
C = self.codomain()
285
D = self.domain()
286
O, X = D.optimized()
287
x = D(x)
288
if O is D:
289
x = x.lift()
290
else:
291
# Now we have to transform x so that it is in the optimized representation.
292
x = D.V().coordinate_vector(x.lift()) * X
293
return C(self._phi(x))
294
295
def kernel(self):
296
"""
297
Compute the kernel of this homomorphism.
298
299
EXAMPLES::
300
301
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
302
sage: Q = V/W; Q
303
Finitely generated module V/W over Integer Ring with invariants (4, 12)
304
sage: Q.hom([0, Q.1]).kernel()
305
Finitely generated module V/W over Integer Ring with invariants (4)
306
sage: A = Q.hom([Q.0, 0]).kernel(); A
307
Finitely generated module V/W over Integer Ring with invariants (12)
308
sage: Q.1 in A
309
True
310
sage: phi = Q.hom([Q.0-3*Q.1, Q.0+Q.1])
311
sage: A = phi.kernel(); A
312
Finitely generated module V/W over Integer Ring with invariants (4)
313
sage: phi(A)
314
Finitely generated module V/W over Integer Ring with invariants ()
315
"""
316
# The kernel is just got by taking the inverse image of the submodule W
317
# of the codomain quotient object.
318
V = self._phi.inverse_image(self.codomain().W())
319
D = self.domain()
320
V = D.W() + V
321
return D._module_constructor(V, D.W(), check=fgp_module.DEBUG)
322
323
def inverse_image(self, A):
324
"""
325
Given a submodule A of the codomain of this morphism, return
326
the inverse image of A under this morphism.
327
328
EXAMPLES::
329
330
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W; Q
331
Finitely generated module V/W over Integer Ring with invariants (4, 12)
332
sage: phi = Q.hom([0, Q.1])
333
sage: phi.inverse_image(Q.submodule([]))
334
Finitely generated module V/W over Integer Ring with invariants (4)
335
sage: phi.kernel()
336
Finitely generated module V/W over Integer Ring with invariants (4)
337
sage: phi.inverse_image(phi.codomain())
338
Finitely generated module V/W over Integer Ring with invariants (4, 12)
339
340
sage: phi.inverse_image(Q.submodule([Q.0]))
341
Finitely generated module V/W over Integer Ring with invariants (4)
342
sage: phi.inverse_image(Q.submodule([Q.1]))
343
Finitely generated module V/W over Integer Ring with invariants (4, 12)
344
345
sage: phi.inverse_image(ZZ^3)
346
Traceback (most recent call last):
347
...
348
TypeError: A must be a finitely generated quotient module
349
sage: phi.inverse_image(ZZ^3 / W.scale(2))
350
Traceback (most recent call last):
351
...
352
ValueError: A must be a submodule of the codomain
353
"""
354
from fgp_module import is_FGP_Module
355
if not is_FGP_Module(A):
356
raise TypeError, "A must be a finitely generated quotient module"
357
if not A.is_submodule(self.codomain()):
358
raise ValueError, "A must be a submodule of the codomain"
359
V = self._phi.inverse_image(A.V())
360
D = self.domain()
361
V = D.W() + V
362
return D._module_constructor(V, D.W(), check=fgp_module.DEBUG)
363
364
def image(self):
365
"""
366
Compute the image of this homomorphism.
367
368
EXAMPLES::
369
370
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
371
sage: Q = V/W; Q
372
Finitely generated module V/W over Integer Ring with invariants (4, 12)
373
sage: Q.hom([Q.0+3*Q.1, -Q.1]).image()
374
Finitely generated module V/W over Integer Ring with invariants (4, 12)
375
sage: Q.hom([3*Q.1, Q.1]).image()
376
Finitely generated module V/W over Integer Ring with invariants (12)
377
"""
378
V = self._phi.image() + self.codomain().W()
379
W = V.intersection(self.codomain().W())
380
return self.codomain()._module_constructor(V, W, check=fgp_module.DEBUG)
381
382
def lift(self, x):
383
"""
384
Given an element x in the codomain of self, if possible find an
385
element y in the domain such that self(y) == x. Raise a ValueError
386
if no such y exists.
387
388
INPUT:
389
390
- ``x`` -- element of the codomain of self.
391
392
EXAMPLES::
393
394
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2])
395
sage: Q=V/W; phi = Q.hom([2*Q.0, Q.1])
396
sage: phi.lift(Q.1)
397
(0, 1)
398
sage: phi.lift(Q.0)
399
Traceback (most recent call last):
400
...
401
ValueError: no lift of element to domain
402
sage: phi.lift(2*Q.0)
403
(1, 0)
404
sage: phi.lift(2*Q.0+Q.1)
405
(1, 1)
406
sage: V = span([[5, -1/2]],ZZ); W = span([[20,-2]],ZZ); Q = V/W; phi=Q.hom([2*Q.0])
407
sage: x = phi.image().0; phi(phi.lift(x)) == x
408
True
409
410
"""
411
x = self.codomain()(x)
412
413
# We view self as a map V/W --> V'/W', where V/W is the
414
# optimized representation (which is fine to work with since
415
# there is a lift to the optimized representation if and only
416
# if there is a lift to the non-optimized representation).
417
CD = self.codomain()
418
A = self._phi.matrix()
419
try:
420
H, U = self.__lift_data
421
except AttributeError:
422
# Get the matrix of self: V --> V' wrt the basis for V and V'.
423
424
# Stack it on top of the basis for W'.
425
Wp = CD.V().coordinate_module(CD.W()).basis_matrix()
426
B = A.stack(Wp)
427
428
# Compute Hermite form of C with transformation
429
H, U = B.hermite_form(transformation=True)
430
self.__lift_data = H, U
431
432
# write x in terms of the basis for V.
433
w = CD.V().coordinate_vector(x.lift())
434
435
# Solve z*H = w.
436
try:
437
z = H.solve_left(w)
438
if z.denominator() != 1:
439
raise ValueError
440
except ValueError:
441
raise ValueError, "no lift of element to domain"
442
443
# Write back in terms of rows of B, and delete rows not corresponding to A,
444
# since those corresponding to relations
445
v = (z*U)[:A.nrows()]
446
447
# Take the linear combination that v defines.
448
y = v*self.domain().optimized()[0].V().basis_matrix()
449
450
# Return the finitely generated module element defined by y.
451
y = self.domain()(y)
452
assert self(y) == x, "bug in phi.lift()"
453
return y
454
455
from sage.categories.homset import Homset
456
457
import weakref
458
_fgp_homset = weakref.WeakValueDictionary()
459
def FGP_Homset(X, Y):
460
"""
461
EXAMPLES::
462
463
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W
464
sage: Q.Hom(Q) # indirect doctest
465
Set of Morphisms from Finitely generated module V/W over Integer Ring with invariants (4, 12) to Finitely generated module V/W over Integer Ring with invariants (4, 12) in Category of modules over Integer Ring
466
sage: True # Q.Hom(Q) is Q.Hom(Q)
467
True
468
sage: type(Q.Hom(Q))
469
<class 'sage.modules.fg_pid.fgp_morphism.FGP_Homset_class_with_category'>
470
"""
471
key = (X,Y)
472
try: return _fgp_homset[key]
473
except KeyError: pass
474
H = FGP_Homset_class(X, Y)
475
# Caching breaks tests in fgp_module.
476
# _fgp_homset[key] = H
477
return H
478
479
480
class FGP_Homset_class(Homset):
481
def __init__(self, X, Y):
482
"""
483
EXAMPLES::
484
485
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W
486
sage: type(Q.Hom(Q))
487
<class 'sage.modules.fg_pid.fgp_morphism.FGP_Homset_class_with_category'>
488
"""
489
Homset.__init__(self, X, Y)
490
self._populate_coercion_lists_(element_constructor = FGP_Morphism,
491
coerce_list = [])
492
493
def _coerce_map_from_(self, S):
494
"""
495
EXAMPLES::
496
497
sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]); Q = V/W
498
sage: phi = Q.hom([Q.0,Q.0 + 2*Q.1]); psi = loads(dumps(phi))
499
sage: phi.parent()._coerce_map_from_(psi.parent())
500
True
501
sage: phi.parent()._coerce_map_from_(Q.Hom(ZZ^3))
502
False
503
"""
504
# We define this so that morphisms in equal parents canonically coerce,
505
# since otherwise, e.g., the dumps(loads(...)) doctest above would fail.
506
if isinstance(S, FGP_Homset_class) and S == self:
507
return True
508
if self.is_endomorphism_set():
509
R = self.domain().base_ring()
510
return R == S or bool(R._coerce_map_from_(S))
511
return False
512
513
def __call__(self, x):
514
"""
515
Convert x into an fgp morphism.
516
517
EXAMPLES::
518
519
sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ); W = V.span([V.0+2*V.1, 9*V.0+2*V.1, 4*V.2])
520
sage: Q = V/W; H = Q.Hom(Q)
521
sage: H(3)
522
Morphism from module over Integer Ring with invariants (4, 16) to module with invariants (4, 16) that sends the generators to [(3, 0), (0, 3)]
523
"""
524
return FGP_Morphism(self, x)
525
526
527