Skip to content

Commit fc97dcd

Browse files
author
Release Manager
committed
gh-39160: Add _make_named_class_key for several categories, miscellaneous changes Fixes #39154 Let `C = Category.join([NumberFields(), QuotientFields(), MetricSpaces()])`, then there are 4 categories being created * `Modules(C)` * `VectorSpaces(C)` * `Modules(C).Filtered()` * `VectorSpaces(C).Filtered()` The last 2 have type `FilteredModulesCategory`, which inherit from `RegressiveCovariantConstructionCategory, Category_over_base_ring` which in turn inherits from `CategoryWithParameters`. Subclasses of `CategoryWithParameters` need to override `_make_named_class_key` correctly so that category A and B can only return same key if their parent class, element class, subcategory class etc. are identical. But `FilteredModulesCategory` takes `_make_named_class_key` from `Category_over_base_ring`, which returns the base category (`C`) for the key. With this change, the returned key is e.g. `Modules(C).parent_class` and `VectorSpaces(C).parent_class` respectively, which is different. Side note: by how things are implemented in SageMath, `Modules(C)` actually returns the same thing as `VectorSpaces(C)`. The above diagram is only for illustration purpose. Side note 2: `Modules(C).Filtered()` is currently a subcategory of `VectorSpaces(C)`… so the above is not a diamond, rather it's a linear chain. Also make `Modules(Fields())).Filtered()` ≤ `VectorSpaces(Fields())`, and a few other changes e.g. make `_test_category_graph` works for hom categories. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39160 Reported by: user202729 Reviewer(s): Tobias Diez
2 parents 6f699d3 + a7e12d6 commit fc97dcd

File tree

5 files changed

+175
-10
lines changed

5 files changed

+175
-10
lines changed

src/sage/categories/category.py

+110-5
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,22 @@ def all_super_categories(self, proper=False):
943943
appropriate. Simply because lazy attributes are much
944944
faster than any method.
945945
946+
.. NOTE::
947+
948+
This is not the same as the concept of super category in mathematics.
949+
In fact, this is not even the opposite relation of :meth:`is_subcategory`::
950+
951+
sage: A = VectorSpaces(QQ); A
952+
Category of vector spaces over Rational Field
953+
sage: B = VectorSpaces(QQ.category()); B
954+
Category of vector spaces over (number fields and quotient fields and metric spaces)
955+
sage: A.is_subcategory(B)
956+
True
957+
sage: B in A.all_super_categories()
958+
False
959+
960+
.. SEEALSO:: :meth:`_test_category_graph`
961+
946962
EXAMPLES::
947963
948964
sage: C = Rings(); C
@@ -1379,7 +1395,16 @@ def _test_category_graph(self, **options):
13791395
method resolution order of the parent and element
13801396
classes. This method checks this.
13811397
1382-
.. TODO:: currently, this won't work for hom categories.
1398+
Note that if
1399+
:meth:`~sage.structure.category_object.CategoryObject._refine_category_`
1400+
is called at unexpected times, the invariant might be false. Most
1401+
commonly, this happens with rings like ``Zmod(n)`` or ``SR``, where
1402+
a check like ``Zmod(n) in Fields()`` is needed (which checks the primality
1403+
of `n`) to refine their category to be a subcategory of fields.
1404+
1405+
.. SEEALSO::
1406+
1407+
:meth:`CategoryWithParameters._make_named_class_key`
13831408
13841409
EXAMPLES::
13851410
@@ -1615,6 +1640,11 @@ def subcategory_class(self):
16151640
sage: isinstance(AlgebrasWithBasis(QQ), cls)
16161641
True
16171642
1643+
.. NOTE::
1644+
1645+
See the note about :meth:`_test_category_graph` regarding Python
1646+
class hierarchy.
1647+
16181648
TESTS::
16191649
16201650
sage: cls = Algebras(QQ).subcategory_class; cls
@@ -1667,6 +1697,11 @@ def parent_class(self):
16671697
:class:`~sage.categories.bimodules.Bimodules`,
16681698
:class:`~sage.categories.category_types.Category_over_base` and
16691699
:class:`sage.categories.category.JoinCategory`.
1700+
1701+
.. NOTE::
1702+
1703+
See the note about :meth:`_test_category_graph` regarding Python
1704+
class hierarchy.
16701705
"""
16711706
return self._make_named_class('parent_class', 'ParentMethods')
16721707

@@ -1713,6 +1748,11 @@ def element_class(self):
17131748
0
17141749
17151750
.. SEEALSO:: :meth:`parent_class`
1751+
1752+
.. NOTE::
1753+
1754+
See the note about :meth:`_test_category_graph` regarding Python
1755+
class hierarchy.
17161756
"""
17171757
return self._make_named_class('element_class', 'ElementMethods')
17181758

@@ -1757,7 +1797,7 @@ def required_methods(self):
17571797
# Operations on the lattice of categories
17581798
def is_subcategory(self, c):
17591799
"""
1760-
Return ``True`` if ``self`` is naturally embedded as a subcategory of `c`.
1800+
Return ``True`` if there is a natural forgetful functor from ``self`` to `c`.
17611801
17621802
EXAMPLES::
17631803
@@ -2046,13 +2086,18 @@ def _with_axiom(self, axiom):
20462086
Return the subcategory of the objects of ``self`` satisfying
20472087
the given ``axiom``.
20482088
2089+
Note that this is a private method thus should not be directly
2090+
used, see below.
2091+
20492092
INPUT:
20502093
20512094
- ``axiom`` -- string, the name of an axiom
20522095
20532096
EXAMPLES::
20542097
2055-
sage: Sets()._with_axiom("Finite")
2098+
sage: Sets()._with_axiom("Finite") # not idiomatic
2099+
Category of finite sets
2100+
sage: Sets().Finite() # recommended
20562101
Category of finite sets
20572102
20582103
sage: type(Magmas().Finite().Commutative())
@@ -2068,7 +2113,7 @@ def _with_axiom(self, axiom):
20682113
sage: Sets()._with_axiom("Associative")
20692114
Category of sets
20702115
2071-
.. WARNING:: This may be changed in the future to raising an error.
2116+
.. WARNING:: This may be changed in the future to raise an error.
20722117
"""
20732118
return Category.join(self._with_axiom_as_tuple(axiom))
20742119

@@ -2718,6 +2763,10 @@ def _make_named_class(self, name, method_provider, cache=False, **options):
27182763
27192764
It is assumed that this method is only called from a lazy
27202765
attribute whose name coincides with the given ``name``.
2766+
Currently, this means :meth:`Category.subcategory_class`,
2767+
:meth:`Category.parent_class` or :meth:`element_class`.
2768+
2769+
Subclasses need to implement :meth:`_make_named_class_key`.
27212770
27222771
OUTPUT:
27232772
@@ -2810,6 +2859,10 @@ def _make_named_class(self, name, method_provider, cache=False, **options):
28102859
pass
28112860
result = Category._make_named_class(self, name, method_provider,
28122861
cache=cache, **options)
2862+
if key[2] != self._make_named_class_key(name):
2863+
# the object in the parameter may have had its category refined, which might modify the key
2864+
# throw result away and recompute
2865+
return self._make_named_class(name, method_provider, cache=cache, **options)
28132866
self._make_named_class_cache[key] = result
28142867
return result
28152868

@@ -2818,6 +2871,50 @@ def _make_named_class_key(self, name):
28182871
r"""
28192872
Return what the element/parent/... class depend on.
28202873
2874+
This method starts as an optimization to allow different related
2875+
categories to share the Python types, see :issue:`11935`.
2876+
However, because of the guarantees stated in :meth:`Category._test_category_graph`,
2877+
the following rules must be followed.
2878+
2879+
- If two categories have different lists of supercategories, they must return
2880+
different keys::
2881+
2882+
sage: Zmod(5) in Fields()
2883+
True
2884+
sage: Algebras(Zmod(5)).all_super_categories()
2885+
[..., Category of vector spaces over Ring of integers modulo 5, ...]
2886+
sage: Zmod(6) in Fields()
2887+
False
2888+
sage: Algebras(Zmod(6)).all_super_categories() # of course don't have category of vector spaces
2889+
[..., Category of modules over Ring of integers modulo 6, ...]
2890+
sage: # therefore:
2891+
sage: Algebras(Zmod(5))._make_named_class_key("parent_class") != Algebras(Zmod(6))._make_named_class_key("parent_class")
2892+
True
2893+
sage: Algebras(Zmod(5)).parent_class != Algebras(Zmod(6)).parent_class
2894+
True
2895+
2896+
- If category ``A`` is a supercategory of category ``B``,
2897+
and category ``B`` uses the optimization, then so must ``A``.
2898+
2899+
For example, ``Modules(ZZ)`` is a supercategory of ``Algebras(ZZ)``,
2900+
and ``Algebras(ZZ)`` implements the optimization::
2901+
2902+
sage: from sage.categories.category import CategoryWithParameters
2903+
sage: isinstance(Algebras(ZZ), CategoryWithParameters)
2904+
True
2905+
sage: Algebras(ZZ).parent_class is Algebras(ZZ.category()).parent_class
2906+
True
2907+
sage: Modules(ZZ) in Algebras(ZZ).all_super_categories()
2908+
True
2909+
2910+
This forces ``Modules(ZZ)`` to also implement the optimization::
2911+
2912+
sage: Modules(ZZ).parent_class is Modules(ZZ.category()).parent_class
2913+
True
2914+
2915+
As a complication, computing the exact category might require some potentially
2916+
expensive test. See :meth:`Category._test_category_graph` for more details.
2917+
28212918
INPUT:
28222919
28232920
- ``name`` -- string; the name of the class as an attribute
@@ -2826,6 +2923,9 @@ def _make_named_class_key(self, name):
28262923
.. SEEALSO::
28272924
28282925
- :meth:`_make_named_class`
2926+
2927+
The following can be read for typical implementations of this method.
2928+
28292929
- :meth:`sage.categories.category_types.Category_over_base._make_named_class_key`
28302930
- :meth:`sage.categories.bimodules.Bimodules._make_named_class_key`
28312931
- :meth:`JoinCategory._make_named_class_key`
@@ -3064,6 +3164,9 @@ def _with_axiom(self, axiom):
30643164
"""
30653165
Return the category obtained by adding an axiom to ``self``.
30663166
3167+
As mentioned in :meth:`Category._with_axiom`, this method should not be used directly
3168+
except in internal code.
3169+
30673170
.. NOTE::
30683171
30693172
This is just an optimization of
@@ -3073,7 +3176,9 @@ def _with_axiom(self, axiom):
30733176
EXAMPLES::
30743177
30753178
sage: C = Category.join([Monoids(), Posets()])
3076-
sage: C._with_axioms(["Finite"])
3179+
sage: C._with_axioms(["Finite"]) # not idiomatic
3180+
Join of Category of finite monoids and Category of finite posets
3181+
sage: C.Finite() # recommended
30773182
Join of Category of finite monoids and Category of finite posets
30783183
30793184
TESTS:

src/sage/categories/filtered_modules.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,54 @@ def _repr_object_names(self):
6767
"""
6868
return "filtered {}".format(self.base_category()._repr_object_names())
6969

70+
def _make_named_class_key(self, name):
71+
r"""
72+
Return what the element/parent/... classes depend on.
73+
74+
.. SEEALSO::
75+
76+
- :meth:`.CategoryWithParameters._make_named_class_key`
77+
78+
EXAMPLES::
79+
80+
sage: Modules(ZZ).Filtered()._make_named_class_key('element_class')
81+
<class 'sage.categories.modules.Modules.element_class'>
82+
83+
Note that we cannot simply return the base as in
84+
:meth:`.Category_over_base._make_named_class_key` because of the following
85+
(see :issue:`39154`)::
86+
87+
sage: VectorSpacesQQ = VectorSpaces(QQ); VectorSpacesQQ
88+
Category of vector spaces over Rational Field
89+
sage: # ModulesQQ = Modules(QQ) # doesn't work because...
90+
sage: Modules(QQ) is VectorSpacesQQ
91+
True
92+
sage: ModulesQQ = VectorSpacesQQ.super_categories()[0]; ModulesQQ
93+
Category of modules over Rational Field
94+
sage: VectorSpacesQQ.Filtered()
95+
Category of filtered vector spaces over Rational Field
96+
sage: ModulesQQ.Filtered()
97+
Category of filtered modules over Rational Field
98+
sage: VectorSpacesQQ.Filtered()._make_named_class_key('parent_class')
99+
<class 'sage.categories.vector_spaces.VectorSpaces.parent_class'>
100+
sage: ModulesQQ.Filtered()._make_named_class_key('parent_class')
101+
<class 'sage.categories.modules.Modules.parent_class'>
102+
sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') !=
103+
....: ModulesQQ.Filtered()._make_named_class_key('parent_class'))
104+
sage: VectorSpacesQQ.Filtered().parent_class
105+
<class 'sage.categories.vector_spaces.VectorSpaces.Filtered.parent_class'>
106+
sage: ModulesQQ.Filtered().parent_class
107+
<class 'sage.categories.filtered_modules.FilteredModules.parent_class'>
108+
109+
Nevertheless, as explained in :meth:`.Category_over_base._make_named_class_key`,
110+
``Modules(QQ).Filtered()`` and ``Modules(QQ.category()).Filtered()`` must have
111+
the same parent class::
112+
113+
sage: Modules(QQ).Filtered().parent_class == Modules(QQ.category()).Filtered().parent_class
114+
True
115+
"""
116+
return getattr(self._base_category, name)
117+
70118

71119
class FilteredModules(FilteredModulesCategory):
72120
r"""
@@ -122,8 +170,9 @@ def extra_super_categories(self):
122170
"""
123171
from sage.categories.modules import Modules
124172
from sage.categories.fields import Fields
173+
from sage.categories.category import Category
125174
base_ring = self.base_ring()
126-
if base_ring in Fields():
175+
if base_ring in Fields() or (isinstance(base_ring, Category) and base_ring.is_subcategory(Fields())):
127176
return [Modules(base_ring)]
128177
else:
129178
return []

src/sage/categories/homset.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def Hom(X, Y, category=None, check=True):
396396
"""
397397
# This should use cache_function instead
398398
# However some special handling is currently needed for
399-
# domains/docomains that break the unique parent condition. Also,
399+
# domains/codomains that break the unique parent condition. Also,
400400
# at some point, it somehow broke the coercion (see e.g. sage -t
401401
# sage.rings.real_mpfr). To be investigated.
402402
global _cache

src/sage/categories/homsets.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
# *****************************************************************************
1111

1212
from sage.misc.cachefunc import cached_method
13-
from sage.categories.category import Category, JoinCategory
13+
from sage.categories.category import Category, JoinCategory, CategoryWithParameters
1414
from sage.categories.category_singleton import Category_singleton
1515
from sage.categories.category_with_axiom import CategoryWithAxiom
1616
from sage.categories.covariant_functorial_construction import FunctorialConstructionCategory
1717

1818

19-
class HomsetsCategory(FunctorialConstructionCategory):
19+
class HomsetsCategory(FunctorialConstructionCategory, CategoryWithParameters):
2020

2121
_functor_category = "Homsets"
2222

@@ -155,6 +155,17 @@ def base(self):
155155
return C.base()
156156
raise AttributeError("This hom category has no base")
157157

158+
def _make_named_class_key(self, name):
159+
r"""
160+
Return what the element/parent/... classes depend on.
161+
162+
.. SEEALSO::
163+
164+
- :meth:`CategoryWithParameters`
165+
- :meth:`CategoryWithParameters._make_named_class_key`
166+
"""
167+
return getattr(self.base_category(), name)
168+
158169

159170
class HomsetsOf(HomsetsCategory):
160171
"""

src/sage/groups/perm_gps/permgroup_named.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3514,7 +3514,7 @@ class SmallPermutationGroup(PermutationGroup_generic):
35143514
[ 2 0 -1 2 0 -1]
35153515
sage: def numgps(n): return ZZ(libgap.NumberSmallGroups(n))
35163516
sage: all(SmallPermutationGroup(n,k).id() == [n,k]
3517-
....: for n in [1..64] for k in [1..numgps(n)])
3517+
....: for n in [1..64] for k in [1..numgps(n)]) # long time (180s)
35183518
True
35193519
sage: H = SmallPermutationGroup(6,1)
35203520
sage: H.is_abelian()

0 commit comments

Comments
 (0)