-
-
Notifications
You must be signed in to change notification settings - Fork 397
/
functor.pyx
691 lines (531 loc) · 20.7 KB
/
functor.pyx
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
"""
Functors
AUTHORS:
- David Kohel and William Stein
- David Joyner (2005-12-17): examples
- Robert Bradshaw (2007-06-23): Pyrexify
- Simon King (2010-04-30): more examples, several bug fixes,
re-implementation of the default call method,
making functors applicable to morphisms (not only to objects)
- Simon King (2010-12): Pickling of functors without loosing domain and codomain
"""
# ****************************************************************************
# Copyright (C) 2005 David Kohel <kohel@maths.usyd.edu> and
# William Stein <wstein@math.ucsd.edu>
#
# Distributed under the terms of the GNU General Public License (GPL)
#
# This code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See the GNU General Public License for more details; the full text
# is available at:
#
# https://www.gnu.org/licenses/
# ****************************************************************************
from . import category
def _Functor_unpickle(Cl, D, domain, codomain):
"""
Generic unpickling function for functors.
AUTHOR:
- Simon King (2010-12): :trac:`10460`
EXAMPLES::
sage: R.<x,y> = InfinitePolynomialRing(QQ)
sage: F = R.construction()[0]
sage: F == loads(dumps(F))
True
sage: F.domain(), loads(dumps(F)).domain()
(Category of rings, Category of rings)
"""
F = Functor.__new__(Cl)
Functor.__init__(F,domain,codomain)
for s,v in D:
setattr(F,s,v)
return F
cdef class Functor(SageObject):
"""
A class for functors between two categories
NOTE:
- In the first place, a functor is given by its domain and codomain,
which are both categories.
- When defining a sub-class, the user should not implement a call method.
Instead, one should implement three methods, which are composed in the
default call method:
- ``_coerce_into_domain(self, x)``: Return an object of ``self``'s
domain, corresponding to ``x``, or raise a ``TypeError``.
- Default: Raise ``TypeError`` if ``x`` is not in ``self``'s domain.
- ``_apply_functor(self, x)``: Apply ``self`` to an object ``x`` of
``self``'s domain.
- Default: Conversion into ``self``'s codomain.
- ``_apply_functor_to_morphism(self, f)``: Apply ``self`` to a morphism
``f`` in ``self``'s domain.
- Default: Return ``self(f.domain()).hom(f,self(f.codomain()))``.
EXAMPLES::
sage: rings = Rings()
sage: abgrps = CommutativeAdditiveGroups()
sage: F = ForgetfulFunctor(rings, abgrps)
sage: F.domain()
Category of rings
sage: F.codomain()
Category of commutative additive groups
sage: from sage.categories.functor import is_Functor
sage: is_Functor(F)
True
sage: I = IdentityFunctor(abgrps)
sage: I
The identity functor on Category of commutative additive groups
sage: I.domain()
Category of commutative additive groups
sage: is_Functor(I)
True
Note that by default, an instance of the class Functor is coercion
from the domain into the codomain. The above subclasses overloaded
this behaviour. Here we illustrate the default::
sage: from sage.categories.functor import Functor
sage: F = Functor(Rings(),Fields())
sage: F
Functor from Category of rings to Category of fields
sage: F(ZZ)
Rational Field
sage: F(GF(2))
Finite Field of size 2
Functors are not only about the objects of a category, but also about
their morphisms. We illustrate it, again, with the coercion functor
from rings to fields.
::
sage: R1.<x> = ZZ[]
sage: R2.<a,b> = QQ[]
sage: f = R1.hom([a+b],R2)
sage: f
Ring morphism:
From: Univariate Polynomial Ring in x over Integer Ring
To: Multivariate Polynomial Ring in a, b over Rational Field
Defn: x |--> a + b
sage: F(f)
Ring morphism:
From: Fraction Field of Univariate Polynomial Ring in x over Integer Ring
To: Fraction Field of Multivariate Polynomial Ring in a, b over Rational Field
Defn: x |--> a + b
sage: F(f)(1/x)
1/(a + b)
We can also apply a polynomial ring construction functor to our homomorphism. The
result is a homomorphism that is defined on the base ring::
sage: F = QQ['t'].construction()[0]
sage: F
Poly[t]
sage: F(f)
Ring morphism:
From: Univariate Polynomial Ring in t over Univariate Polynomial Ring in x over Integer Ring
To: Univariate Polynomial Ring in t over Multivariate Polynomial Ring in a, b over Rational Field
Defn: Induced from base ring by
Ring morphism:
From: Univariate Polynomial Ring in x over Integer Ring
To: Multivariate Polynomial Ring in a, b over Rational Field
Defn: x |--> a + b
sage: p = R1['t']('(-x^2 + x)*t^2 + (x^2 - x)*t - 4*x^2 - x + 1')
sage: F(f)(p)
(-a^2 - 2*a*b - b^2 + a + b)*t^2 + (a^2 + 2*a*b + b^2 - a - b)*t - 4*a^2 - 8*a*b - 4*b^2 - a - b + 1
"""
def __init__(self, domain, codomain):
"""
TESTS::
sage: from sage.categories.functor import Functor
sage: F = Functor(Rings(),Fields())
sage: F
Functor from Category of rings to Category of fields
sage: F(ZZ)
Rational Field
sage: F(GF(2))
Finite Field of size 2
"""
if not category.is_Category(domain):
raise TypeError("domain (=%s) must be a category" % domain)
if not category.is_Category(codomain):
raise TypeError("codomain (=%s) must be a category" % codomain)
self.__domain = domain
self.__codomain = codomain
def __hash__(self):
r"""
TESTS::
sage: from sage.categories.functor import Functor
sage: F = Functor(Rings(), Fields())
sage: hash(F) # random
42
"""
return hash(self.__domain) ^ hash(self.__codomain)
def __reduce__(self):
"""
Generic pickling of functors.
AUTHOR:
- Simon King (2010-12): :trac:`10460`
TESTS::
sage: from sage.categories.pushout import CompositeConstructionFunctor
sage: F = CompositeConstructionFunctor(QQ.construction()[0],ZZ['x'].construction()[0],QQ.construction()[0],ZZ['y'].construction()[0])
sage: F == loads(dumps(F))
True
sage: F.codomain()
Category of rings
"""
return (_Functor_unpickle,
(self.__class__, list(self.__dict__.items()),
self.__domain, self.__codomain))
def _apply_functor(self, x):
"""
Apply the functor to an object of ``self``'s domain.
NOTE:
Each subclass of :class:`Functor` should overload this method. By default,
this method coerces into the codomain, without checking whether the
argument belongs to the domain.
TESTS::
sage: from sage.categories.functor import Functor
sage: F = Functor(FiniteFields(),Fields())
sage: F._apply_functor(ZZ)
Rational Field
"""
return self.__codomain(x)
def _apply_functor_to_morphism(self, f):
"""
Apply the functor to a morphism between two objects of ``self``'s domain.
NOTE:
Each subclass of :class:`Functor` should overload this method. By
default, this method coerces into the codomain, without checking
whether the argument belongs to the domain.
TESTS::
sage: from sage.categories.functor import Functor
sage: F = Functor(Rings(),Fields())
sage: k.<a> = GF(25)
sage: f = k.hom([-a-4])
sage: R.<t> = k[]
sage: fR = R.hom(f,R)
sage: fF = F(fR) # indirect doctest
sage: fF
Ring endomorphism of Fraction Field of Univariate Polynomial Ring in t over Finite Field in a of size 5^2
Defn: Induced from base ring by
Ring endomorphism of Univariate Polynomial Ring in t over Finite Field in a of size 5^2
Defn: Induced from base ring by
Ring endomorphism of Finite Field in a of size 5^2
Defn: a |--> 4*a + 1
sage: fF((a^2+a)*t^2/(a*t - a^2))
((4*a + 2)*t^2)/(t + a + 4)
"""
try:
return self(f.domain()).hom(f, self(f.codomain()))
except Exception:
raise TypeError('unable to transform %s into a morphism in %s' % (f,self.codomain()))
def _coerce_into_domain(self, x):
"""
Interprete the argument as an object of self's domain.
NOTE:
A subclass of :class:`Functor` may overload this method. It should
return an object of self's domain, and should raise a ``TypeError``
if this is impossible.
By default, the argument will not be changed, but a ``TypeError``
will be raised if the argument does not belong to the domain.
TESTS::
sage: from sage.categories.functor import Functor
sage: F = Functor(Fields(),Fields())
sage: F(QQ)
Rational Field
sage: F(ZZ) # indirect doctest
Traceback (most recent call last):
...
TypeError: x (=Integer Ring) is not in Category of fields
"""
if not (x in self.__domain):
raise TypeError("x (=%s) is not in %s" % (x, self.__domain))
return x
def _repr_(self):
"""
TESTS::
sage: from sage.categories.functor import Functor
sage: F = Functor(Rings(),Fields())
sage: F #indirect doctest
Functor from Category of rings to Category of fields
A functor can be renamed if its type is a Python class
(see :trac:`16156`)::
sage: I = IdentityFunctor(Rings()); I
The identity functor on Category of rings
sage: I.rename('Id'); I
Id
"""
return "Functor from %s to %s"%(self.__domain, self.__codomain)
def __call__(self, x):
"""
NOTE:
Implement _coerce_into_domain, _apply_functor and
_apply_functor_to_morphism when subclassing Functor.
TESTS:
The default::
sage: from sage.categories.functor import Functor
sage: F = Functor(Rings(),Fields())
sage: F
Functor from Category of rings to Category of fields
sage: F(ZZ)
Rational Field
sage: F(GF(2))
Finite Field of size 2
Two subclasses::
sage: F1 = ForgetfulFunctor(FiniteFields(),Fields())
sage: F1(GF(5)) #indirect doctest
Finite Field of size 5
sage: F1(ZZ)
Traceback (most recent call last):
...
TypeError: x (=Integer Ring) is not in Category of finite enumerated fields
sage: F2 = IdentityFunctor(Fields())
sage: F2(RR) is RR #indirect doctest
True
sage: F2(ZZ['x','y'])
Traceback (most recent call last):
...
TypeError: x (=Multivariate Polynomial Ring in x, y over Integer Ring) is not in Category of fields
The last example shows that it is tested whether the result of
applying the functor lies in the functor's codomain. Note that
the matrix functor used to be defined similar to this example,
which was fixed in :trac:`8807`::
sage: class IllFunctor(Functor):
....: def __init__(self, m,n):
....: self._m = m
....: self._n = n
....: Functor.__init__(self,Rings(),Rings())
....: def _apply_functor(self, R):
....: return MatrixSpace(R,self._m,self._n)
sage: F = IllFunctor(2,2)
sage: F(QQ)
Full MatrixSpace of 2 by 2 dense matrices over Rational Field
sage: F = IllFunctor(2,3)
sage: F(QQ)
Traceback (most recent call last):
...
TypeError: Functor from Category of rings to Category of rings is ill-defined, since it sends x (=Rational Field) to something that is not in Category of rings.
"""
from sage.categories.morphism import is_Morphism
if is_Morphism(x):
return self._apply_functor_to_morphism(x)
y = self._apply_functor(self._coerce_into_domain(x))
if not ((y in self.__codomain) or (y in self.__codomain.Homsets())):
raise TypeError("%s is ill-defined, since it sends x (=%s) to something that is not in %s." % (repr(self), x, self.__codomain))
return y
def domain(self):
"""
The domain of self
EXAMPLES::
sage: F = ForgetfulFunctor(FiniteFields(),Fields())
sage: F.domain()
Category of finite enumerated fields
"""
return self.__domain
def codomain(self):
"""
The codomain of self
EXAMPLES::
sage: F = ForgetfulFunctor(FiniteFields(),Fields())
sage: F.codomain()
Category of fields
"""
return self.__codomain
def is_Functor(x):
"""
Test whether the argument is a functor
NOTE:
There is a deprecation warning when using it from top level.
Therefore we import it in our doc test.
EXAMPLES::
sage: from sage.categories.functor import is_Functor
sage: F1 = QQ.construction()[0]
sage: F1
FractionField
sage: is_Functor(F1)
True
sage: is_Functor(FractionField)
False
sage: F2 = ForgetfulFunctor(Fields(), Rings())
sage: F2
The forgetful functor from Category of fields to Category of rings
sage: is_Functor(F2)
True
"""
return isinstance(x, Functor)
###########################################
# The natural functors in Sage
###########################################
class ForgetfulFunctor_generic(Functor):
"""
The forgetful functor, i.e., embedding of a subcategory.
NOTE:
Forgetful functors should be created using :func:`ForgetfulFunctor`,
since the init method of this class does not check whether the
domain is a subcategory of the codomain.
EXAMPLES::
sage: F = ForgetfulFunctor(FiniteFields(),Fields()) #indirect doctest
sage: F
The forgetful functor from Category of finite enumerated fields to Category of fields
sage: F(GF(3))
Finite Field of size 3
"""
def __reduce__(self):
"""
EXAMPLES::
sage: F = ForgetfulFunctor(Groups(), Sets())
sage: loads(F.dumps()) == F
True
"""
return ForgetfulFunctor, (self.domain(), self.codomain())
def _repr_(self):
"""
TESTS::
sage: F = ForgetfulFunctor(FiniteFields(),Fields())
sage: F #indirect doctest
The forgetful functor from Category of finite enumerated fields to Category of fields
"""
return "The forgetful functor from %s to %s" % (self.domain(),
self.codomain())
def __eq__(self, other):
"""
NOTE:
It is tested whether the second argument belongs to the class
of forgetful functors and has the same domain and codomain as
self. If the second argument is a functor of a different class
but happens to be a forgetful functor, both arguments will
still be considered as being *different*.
TESTS::
sage: F1 = ForgetfulFunctor(FiniteFields(),Fields())
This is to test against a bug occuring in a previous version
(see :trac:`8800`)::
sage: F1 == QQ #indirect doctest
False
We now compare with the fraction field functor, that has a
different domain::
sage: F2 = QQ.construction()[0]
sage: F1 == F2 #indirect doctest
False
"""
from sage.categories.pushout import IdentityConstructionFunctor
if not isinstance(other, (self.__class__, IdentityConstructionFunctor)):
return False
return (self.domain() == other.domain() and
self.codomain() == other.codomain())
def __ne__(self, other):
"""
Return whether ``self`` is not equal to ``other``.
EXAMPLES::
sage: F1 = ForgetfulFunctor(FiniteFields(),Fields())
sage: F1 != F1
False
sage: F1 != QQ
True
"""
return not self == other
class IdentityFunctor_generic(ForgetfulFunctor_generic):
"""
Generic identity functor on any category
NOTE:
This usually is created using :func:`IdentityFunctor`.
EXAMPLES::
sage: F = IdentityFunctor(Fields()) #indirect doctest
sage: F
The identity functor on Category of fields
sage: F(RR) is RR
True
sage: F(ZZ)
Traceback (most recent call last):
...
TypeError: x (=Integer Ring) is not in Category of fields
TESTS::
sage: R = IdentityFunctor(Rings())
sage: P, _ = QQ['t'].construction()
sage: R == P
False
sage: P == R
False
sage: R == QQ
False
"""
def __init__(self, C):
"""
TESTS::
sage: from sage.categories.functor import IdentityFunctor_generic
sage: F = IdentityFunctor_generic(Groups())
sage: F == IdentityFunctor(Groups())
True
sage: F
The identity functor on Category of groups
"""
ForgetfulFunctor_generic.__init__(self, C, C)
def __reduce__(self):
"""
EXAMPLES::
sage: F = IdentityFunctor(Groups())
sage: loads(F.dumps()) == F
True
"""
return IdentityFunctor, (self.domain(), )
def _repr_(self):
"""
TESTS::
sage: fields = Fields()
sage: F = IdentityFunctor(fields)
sage: F #indirect doctest
The identity functor on Category of fields
"""
return "The identity functor on %s"%(self.domain())
def _apply_functor(self, x):
"""
Apply the functor to an object of ``self``'s domain.
TESTS::
sage: fields = Fields()
sage: F = IdentityFunctor(fields)
sage: F._apply_functor(QQ)
Rational Field
It is not tested here whether the argument belongs to the domain
(this test is done in the default method ``_coerce_into_domain``)::
sage: F._apply_functor(ZZ)
Integer Ring
"""
return x
def IdentityFunctor(C):
"""
Construct the identity functor of the given category.
INPUT:
A category, ``C``.
OUTPUT:
The identity functor in ``C``.
EXAMPLES::
sage: rings = Rings()
sage: F = IdentityFunctor(rings)
sage: F(ZZ['x','y']) is ZZ['x','y']
True
"""
return IdentityFunctor_generic(C)
def ForgetfulFunctor(domain, codomain):
"""
Construct the forgetful function from one category to another.
INPUT:
``C``, ``D`` - two categories
OUTPUT:
A functor that returns the corresponding object of ``D`` for
any element of ``C``, by forgetting the extra structure.
ASSUMPTION:
The category ``C`` must be a sub-category of ``D``.
EXAMPLES::
sage: rings = Rings()
sage: abgrps = CommutativeAdditiveGroups()
sage: F = ForgetfulFunctor(rings, abgrps)
sage: F
The forgetful functor from Category of rings to Category of commutative additive groups
It would be a mistake to call it in opposite order::
sage: F = ForgetfulFunctor(abgrps, rings)
Traceback (most recent call last):
...
ValueError: Forgetful functor not supported for domain Category of commutative additive groups
If both categories are equal, the forgetful functor is the same as the
identity functor::
sage: ForgetfulFunctor(abgrps, abgrps) == IdentityFunctor(abgrps)
True
"""
if domain == codomain:
return IdentityFunctor(domain)
if not domain.is_subcategory(codomain):
raise ValueError("Forgetful functor not supported for domain %s" % domain)
return ForgetfulFunctor_generic(domain, codomain)