Skip to content

Commit

Permalink
Several fixes and documentation improvements for categories
Browse files Browse the repository at this point in the history
  • Loading branch information
user202729 committed Dec 23, 2024
1 parent c9dd1e8 commit 97db4bf
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 10 deletions.
115 changes: 110 additions & 5 deletions src/sage/categories/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,22 @@ def all_super_categories(self, proper=False):
appropriate. Simply because lazy attributes are much
faster than any method.
.. NOTE::
This is not the same as the concept of super category in mathematics.
In fact, this is not even the opposite relation of :meth:`is_subcategory`::
sage: A = VectorSpaces(QQ); A
Category of vector spaces over Rational Field
sage: B = VectorSpaces(QQ.category()); B
Category of vector spaces over (number fields and quotient fields and metric spaces)
sage: A.is_subcategory(B)
True
sage: B in A.all_super_categories()
False
.. SEEALSO:: :meth:`_test_category_graph`
EXAMPLES::
sage: C = Rings(); C
Expand Down Expand Up @@ -1379,7 +1395,16 @@ def _test_category_graph(self, **options):
method resolution order of the parent and element
classes. This method checks this.
.. TODO:: currently, this won't work for hom categories.
Note that if
:meth:`~sage.structure.category_object.CategoryObject._refine_category_`
is called at unexpected times, the invariant might be false. Most
commonly, this happens with rings like ``Zmod(n)`` or ``SR``, where
a check like ``Zmod(n) in Fields()`` is needed (which checks the primality
of `n`) to refine their category to be a subcategory of fields.
.. SEEALSO::
:meth:`CategoryWithParameters._make_named_class_key`
EXAMPLES::
Expand Down Expand Up @@ -1615,6 +1640,11 @@ def subcategory_class(self):
sage: isinstance(AlgebrasWithBasis(QQ), cls)
True
.. NOTE::
See the note about :meth:`_test_category_graph` regarding Python
class hierarchy.
TESTS::
sage: cls = Algebras(QQ).subcategory_class; cls
Expand Down Expand Up @@ -1667,6 +1697,11 @@ def parent_class(self):
:class:`~sage.categories.bimodules.Bimodules`,
:class:`~sage.categories.category_types.Category_over_base` and
:class:`sage.categories.category.JoinCategory`.
.. NOTE::
See the note about :meth:`_test_category_graph` regarding Python
class hierarchy.
"""
return self._make_named_class('parent_class', 'ParentMethods')

Expand Down Expand Up @@ -1713,6 +1748,11 @@ def element_class(self):
0
.. SEEALSO:: :meth:`parent_class`
.. NOTE::
See the note about :meth:`_test_category_graph` regarding Python
class hierarchy.
"""
return self._make_named_class('element_class', 'ElementMethods')

Expand Down Expand Up @@ -1757,7 +1797,7 @@ def required_methods(self):
# Operations on the lattice of categories
def is_subcategory(self, c):
"""
Return ``True`` if ``self`` is naturally embedded as a subcategory of `c`.
Return ``True`` if there is a natural forgetful functor from ``self`` to `c`.
EXAMPLES::
Expand Down Expand Up @@ -2046,13 +2086,18 @@ def _with_axiom(self, axiom):
Return the subcategory of the objects of ``self`` satisfying
the given ``axiom``.
Note that this is a private method thus should not be directly
used, see below.
INPUT:
- ``axiom`` -- string, the name of an axiom
EXAMPLES::
sage: Sets()._with_axiom("Finite")
sage: Sets()._with_axiom("Finite") # not idiomatic
Category of finite sets
sage: Sets().Finite() # recommended
Category of finite sets
sage: type(Magmas().Finite().Commutative())
Expand All @@ -2068,7 +2113,7 @@ def _with_axiom(self, axiom):
sage: Sets()._with_axiom("Associative")
Category of sets
.. WARNING:: This may be changed in the future to raising an error.
.. WARNING:: This may be changed in the future to raise an error.
"""
return Category.join(self._with_axiom_as_tuple(axiom))

Expand Down Expand Up @@ -2718,6 +2763,10 @@ def _make_named_class(self, name, method_provider, cache=False, **options):
It is assumed that this method is only called from a lazy
attribute whose name coincides with the given ``name``.
Currently, this means :meth:`Category.subcategory_class`,
:meth:`Category.parent_class` or :meth:`element_class`.
Subclasses need to implement :meth:`_make_named_class_key`.
OUTPUT:
Expand Down Expand Up @@ -2810,6 +2859,10 @@ def _make_named_class(self, name, method_provider, cache=False, **options):
pass
result = Category._make_named_class(self, name, method_provider,
cache=cache, **options)
if key[2] != self._make_named_class_key(name):
# the object in the parameter may have had its category refined, which might modify the key
# throw result away and recompute
return self._make_named_class(name, method_provider, cache=cache, **options)
self._make_named_class_cache[key] = result
return result

Expand All @@ -2818,6 +2871,50 @@ def _make_named_class_key(self, name):
r"""
Return what the element/parent/... class depend on.
This method starts as an optimization to allow different related
categories to share the Python types, see :issue:`11935`.
However, because of the guarantees stated in :meth:`Category._test_category_graph`,
the following rules must be followed::
- If two categories have different lists of supercategories, they must return
different keys::
sage: Zmod(5) in Fields()
True
sage: Algebras(Zmod(5)).all_super_categories()
[..., Category of vector spaces over Ring of integers modulo 5, ...]
sage: Zmod(6) in Fields()
False
sage: Algebras(Zmod(6)).all_super_categories() # of course don't have category of vector spaces
[..., Category of modules over Ring of integers modulo 6, ...]
sage: # therefore:
sage: Algebras(Zmod(5))._make_named_class_key("parent_class") != Algebras(Zmod(6))._make_named_class_key("parent_class")
True
sage: Algebras(Zmod(5)).parent_class != Algebras(Zmod(6)).parent_class
True
- If category ``A`` is a supercategory of category ``B``,
and category ``B`` uses the optimization, then so must ``A``.
For example, ``Modules(ZZ)`` is a supercategory of ``Algebras(ZZ)``,
and ``Algebras(ZZ)`` implements the optimization::
sage: from sage.categories.category import CategoryWithParameters
sage: isinstance(Algebras(ZZ), CategoryWithParameters)
True
sage: Algebras(ZZ).parent_class is Algebras(ZZ.category()).parent_class
True
sage: Modules(ZZ) in Algebras(ZZ).all_super_categories()
True
This forces ``Modules(ZZ)`` to also implement the optimization::
sage: Modules(ZZ).parent_class is Modules(ZZ.category()).parent_class
True
As a complication, computing the exact category might require some potentially
expensive test. See :meth:`Category._test_category_graph` for more details.
INPUT:
- ``name`` -- string; the name of the class as an attribute
Expand All @@ -2826,6 +2923,9 @@ def _make_named_class_key(self, name):
.. SEEALSO::
- :meth:`_make_named_class`
The following can be read for typical implementations of this method.
- :meth:`sage.categories.category_types.Category_over_base._make_named_class_key`
- :meth:`sage.categories.bimodules.Bimodules._make_named_class_key`
- :meth:`JoinCategory._make_named_class_key`
Expand Down Expand Up @@ -3064,6 +3164,9 @@ def _with_axiom(self, axiom):
"""
Return the category obtained by adding an axiom to ``self``.
As mentioned in :meth:`Category._with_axiom`, this method should not be used directly
except in internal code.
.. NOTE::
This is just an optimization of
Expand All @@ -3073,7 +3176,9 @@ def _with_axiom(self, axiom):
EXAMPLES::
sage: C = Category.join([Monoids(), Posets()])
sage: C._with_axioms(["Finite"])
sage: C._with_axioms(["Finite"]) # not idiomatic
Join of Category of finite monoids and Category of finite posets
sage: C.Finite() # recommended
Join of Category of finite monoids and Category of finite posets
TESTS:
Expand Down
51 changes: 50 additions & 1 deletion src/sage/categories/filtered_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ def _repr_object_names(self):
"""
return "filtered {}".format(self.base_category()._repr_object_names())

def _make_named_class_key(self, name):
r"""
Return what the element/parent/... classes depend on.
.. SEEALSO::
- :meth:`.CategoryWithParameters._make_named_class_key`
EXAMPLES::
sage: Modules(ZZ).Filtered()._make_named_class_key('element_class')
<class 'sage.categories.modules.Modules.element_class'>
Note that we cannot simply return the base as in
:meth:`.Category_over_base._make_named_class_key` because of the following
(see :issue:`39154`)::
sage: VectorSpacesQQ = VectorSpaces(QQ); VectorSpacesQQ
Category of vector spaces over Rational Field
sage: # ModulesQQ = Modules(QQ) # doesn't work because...
sage: Modules(QQ) is VectorSpacesQQ
True
sage: ModulesQQ = VectorSpacesQQ.super_categories()[0]; ModulesQQ
Category of modules over Rational Field
sage: VectorSpacesQQ.Filtered()
Category of filtered vector spaces over Rational Field
sage: ModulesQQ.Filtered()
Category of filtered modules over Rational Field
sage: VectorSpacesQQ.Filtered()._make_named_class_key('parent_class')
<class 'sage.categories.vector_spaces.VectorSpaces.parent_class'>
sage: ModulesQQ.Filtered()._make_named_class_key('parent_class')
<class 'sage.categories.modules.Modules.parent_class'>
sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') !=
....: ModulesQQ.Filtered()._make_named_class_key('parent_class'))
sage: VectorSpacesQQ.Filtered().parent_class
<class 'sage.categories.vector_spaces.VectorSpaces.Filtered.parent_class'>
sage: ModulesQQ.Filtered().parent_class
<class 'sage.categories.filtered_modules.FilteredModules.parent_class'>
Nevertheless, as explained in :meth:`.Category_over_base._make_named_class_key`,
``Modules(QQ).Filtered()`` and ``Modules(QQ.category()).Filtered()`` must have
the same parent class::
sage: Modules(QQ).Filtered().parent_class == Modules(QQ.category()).Filtered().parent_class
True
"""
return getattr(self._base_category, name)


class FilteredModules(FilteredModulesCategory):
r"""
Expand Down Expand Up @@ -122,8 +170,9 @@ def extra_super_categories(self):
"""
from sage.categories.modules import Modules
from sage.categories.fields import Fields
from sage.categories.category import Category
base_ring = self.base_ring()
if base_ring in Fields():
if base_ring in Fields() or (isinstance(base_ring, Category) and base_ring.is_subcategory(Fields())):
return [Modules(base_ring)]
else:
return []
Expand Down
2 changes: 1 addition & 1 deletion src/sage/categories/homset.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def Hom(X, Y, category=None, check=True):
"""
# This should use cache_function instead
# However some special handling is currently needed for
# domains/docomains that break the unique parent condition. Also,
# domains/codomains that break the unique parent condition. Also,
# at some point, it somehow broke the coercion (see e.g. sage -t
# sage.rings.real_mpfr). To be investigated.
global _cache
Expand Down
15 changes: 13 additions & 2 deletions src/sage/categories/homsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
# *****************************************************************************

from sage.misc.cachefunc import cached_method
from sage.categories.category import Category, JoinCategory
from sage.categories.category import Category, JoinCategory, CategoryWithParameters
from sage.categories.category_singleton import Category_singleton
from sage.categories.category_with_axiom import CategoryWithAxiom
from sage.categories.covariant_functorial_construction import FunctorialConstructionCategory


class HomsetsCategory(FunctorialConstructionCategory):
class HomsetsCategory(FunctorialConstructionCategory, CategoryWithParameters):

_functor_category = "Homsets"

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

def _make_named_class_key(self, name):
r"""
Return what the element/parent/... classes depend on.
.. SEEALSO::
- :meth:`CategoryWithParameters`
- :meth:`CategoryWithParameters._make_named_class_key`
"""
return getattr(self.base_category(), name)


class HomsetsOf(HomsetsCategory):
"""
Expand Down
2 changes: 1 addition & 1 deletion src/sage/groups/perm_gps/permgroup_named.py
Original file line number Diff line number Diff line change
Expand Up @@ -3514,7 +3514,7 @@ class SmallPermutationGroup(PermutationGroup_generic):
[ 2 0 -1 2 0 -1]
sage: def numgps(n): return ZZ(libgap.NumberSmallGroups(n))
sage: all(SmallPermutationGroup(n,k).id() == [n,k]

Check warning on line 3516 in src/sage/groups/perm_gps/permgroup_named.py

View workflow job for this annotation

GitHub Actions / test-new

Warning: slow doctest:

slow doctest:

Check warning on line 3516 in src/sage/groups/perm_gps/permgroup_named.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[g-o]*)

Warning: slow doctest:

slow doctest:
....: for n in [1..64] for k in [1..numgps(n)])
....: for n in [1..64] for k in [1..numgps(n)]) # long time (180s)
True
sage: H = SmallPermutationGroup(6,1)
sage: H.is_abelian()
Expand Down

0 comments on commit 97db4bf

Please sign in to comment.