Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add _make_named_class_key for several categories, miscellaneous changes #39160

Merged
merged 5 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -3513,8 +3513,8 @@
[ 2 0 -1 -2 0 1]
[ 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
Loading