From df6293f43ccf74d5f7363bd4946d29b7fe7d3904 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:43:30 +0700 Subject: [PATCH 01/26] Add _make_named_class_key for FilteredModulesCategory --- src/sage/categories/filtered_modules.py | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py index c4ca2db0028..faf39ceece2 100644 --- a/src/sage/categories/filtered_modules.py +++ b/src/sage/categories/filtered_modules.py @@ -67,6 +67,47 @@ 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') + Category of modules over Integer Ring + + 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') + Category of vector spaces over Rational Field + sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') + Category of modules over Rational Field + sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != + ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) + sage: VectorSpacesQQ.Filtered().parent_class + + sage: ModulesQQ.Filtered().parent_class + + """ + return self._base_category + class FilteredModules(FilteredModulesCategory): r""" From c6b3b551e37296497046148eb89bb0d26662fde8 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:23:24 +0700 Subject: [PATCH 02/26] Fix a few things --- src/sage/categories/category.py | 22 +++++++++++++++++++++- src/sage/categories/filtered_modules.py | 11 +++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index feb1b2cb393..f18b6360d01 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -943,6 +943,20 @@ 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 + EXAMPLES:: sage: C = Rings(); C @@ -1757,7 +1771,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:: @@ -2810,6 +2824,12 @@ def _make_named_class(self, name, method_provider, cache=False, **options): pass result = Category._make_named_class(self, name, method_provider, cache=cache, **options) + # the object in the parameter may have had its category refined, which modifies the key, needs to recompute + # (problem with mutable objects) + key = (cls, name, self._make_named_class_key(name)) + if key in self._make_named_class_cache: + # throw result away and use cached value + return self._make_named_class_cache[key] self._make_named_class_cache[key] = result return result diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py index faf39ceece2..a025f129c87 100644 --- a/src/sage/categories/filtered_modules.py +++ b/src/sage/categories/filtered_modules.py @@ -105,8 +105,14 @@ def _make_named_class_key(self, name): sage: ModulesQQ.Filtered().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 """ - return self._base_category + return (type(self._base_category).__base__, super()._make_named_class_key(name)) class FilteredModules(FilteredModulesCategory): @@ -163,8 +169,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 [] From c14e9589fbd8aafbba74b5500f988621ebb7de66 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 00:20:27 +0700 Subject: [PATCH 03/26] Fix tests --- src/sage/categories/filtered_modules.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py index a025f129c87..a3572de40bd 100644 --- a/src/sage/categories/filtered_modules.py +++ b/src/sage/categories/filtered_modules.py @@ -78,7 +78,8 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ).Filtered()._make_named_class_key('element_class') - Category of modules over Integer Ring + (, + Join of Category of Dedekind domains and Category of euclidean domains and Category of noetherian rings and Category of infinite enumerated sets and Category of metric spaces) Note that we cannot simply return the base as in :meth:`.Category_over_base._make_named_class_key` because of the following @@ -96,9 +97,11 @@ def _make_named_class_key(self, name): sage: ModulesQQ.Filtered() Category of filtered modules over Rational Field sage: VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') - Category of vector spaces over Rational Field + (, + Join of Category of number fields and Category of quotient fields and Category of metric spaces) sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') - Category of modules over Rational Field + (, + Join of Category of number fields and Category of quotient fields and Category of metric spaces) sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) sage: VectorSpacesQQ.Filtered().parent_class @@ -111,6 +114,7 @@ def _make_named_class_key(self, name): the same parent class:: sage: Modules(QQ).Filtered().parent_class == Modules(QQ.category()).Filtered().parent_class + True """ return (type(self._base_category).__base__, super()._make_named_class_key(name)) From 8511d7e4cbb6b26d9326184a7a03abeeb8ce149e Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:50:47 +0700 Subject: [PATCH 04/26] Add debug option to check category MRO --- src/sage/categories/category.py | 17 +++++++++++++++-- src/sage/doctest/forker.py | 2 +- src/sage/structure/debug_options.pxd | 1 + src/sage/structure/debug_options.pyx | 11 ++++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index f18b6360d01..d7e615e8ffc 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -113,6 +113,7 @@ from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.debug_options import debug from sage.structure.dynamic_class import DynamicMetaclass, dynamic_class from sage.categories.category_cy_helper import category_sort_key, _sort_uniq, _flatten_categories, join_as_tuple @@ -1682,7 +1683,13 @@ def parent_class(self): :class:`~sage.categories.category_types.Category_over_base` and :class:`sage.categories.category.JoinCategory`. """ - return self._make_named_class('parent_class', 'ParentMethods') + parent_class = self._make_named_class('parent_class', 'ParentMethods') + if debug.test_category_graph: + # see also _test_category_graph() + if parent_class.mro()[1:] != [C.parent_class for C in self._all_super_categories_proper] + [object]: + print(f"Category graph does not match with Python MRO: {parent_class=} {parent_class.mro()=} {self._all_super_categories=}") + return parent_class + @lazy_attribute def element_class(self): @@ -1728,7 +1735,13 @@ def element_class(self): .. SEEALSO:: :meth:`parent_class` """ - return self._make_named_class('element_class', 'ElementMethods') + element_class = self._make_named_class('element_class', 'ElementMethods') + if debug.test_category_graph: + # see also _test_category_graph() + assert self is self._all_super_categories[0] + if element_class.mro()[1:] != [C.element_class for C in self._all_super_categories_proper] + [object]: + print(f"Category graph does not match with Python MRO: {element_class=} {element_class.mro()=} {self._all_super_categories=}") + return element_class @lazy_attribute def morphism_class(self): diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index bf6d49906de..7e3f18cf33d 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -226,7 +226,7 @@ def init_sage(controller=None): # Switch on extra debugging from sage.structure.debug_options import debug - debug.refine_category_hash_check = True + debug.enable_extra_debugging_during_doctest() # We import readline before forking, otherwise Pdb doesn't work # on OS X: https://github.com/sagemath/sage/issues/14289 diff --git a/src/sage/structure/debug_options.pxd b/src/sage/structure/debug_options.pxd index 42a3411e76b..13745d44bb2 100644 --- a/src/sage/structure/debug_options.pxd +++ b/src/sage/structure/debug_options.pxd @@ -2,5 +2,6 @@ cdef class DebugOptions_class: cdef public bint unique_parent_warnings cdef public bint refine_category_hash_check + cdef public bint test_category_graph cdef DebugOptions_class debug diff --git a/src/sage/structure/debug_options.pyx b/src/sage/structure/debug_options.pyx index 78e12faa0ce..105edc5c46f 100644 --- a/src/sage/structure/debug_options.pyx +++ b/src/sage/structure/debug_options.pyx @@ -9,6 +9,8 @@ EXAMPLES:: False sage: debug.refine_category_hash_check True + sage: debug.test_category_graph + True """ #***************************************************************************** @@ -35,8 +37,15 @@ cdef class DebugOptions_class: <... 'sage.structure.debug_options.DebugOptions_class'> """ self.unique_parent_warnings = False - # This one will be enabled during doctests self.refine_category_hash_check = False + self.test_category_graph = True + + def enable_extra_debugging_during_doctest(self): + """ + Function that is called before doctest to enable extra debugging options. + """ + self.refine_category_hash_check = True + self.test_category_graph = True cdef DebugOptions_class debug = DebugOptions_class() From 449e97c888cd523fc28e857de6cf0b046b4bda1d Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 00:20:23 +0700 Subject: [PATCH 05/26] More debug checking --- src/sage/categories/category.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index d7e615e8ffc..624b26e6809 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2831,6 +2831,12 @@ def _make_named_class(self, name, method_provider, cache=False, **options): if isinstance(cls, DynamicMetaclass): cls = cls.__base__ key = (cls, name, self._make_named_class_key(name)) + if debug.test_category_graph and key in self._make_named_class_cache: + new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options) + old_cls = self._make_named_class_cache[key] + if old_cls.mro()[1:] != new_cls.mro()[1:]: + print(f"Categories with same _make_named_class_key has different MRO: {self._all_super_categories=}", + [(i, a, b) for i, (a, b) in enumerate(zip(old_cls.mro()[1:], new_cls.mro()[1:])) if a!=b]) try: return self._make_named_class_cache[key] except KeyError: From 2c64aeefd89223ff9cabf0df4b262b3836811cf4 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 07:53:28 +0700 Subject: [PATCH 06/26] Harmless refactors --- src/sage/categories/category.py | 16 +++++++++++++--- src/sage/categories/category_with_axiom.py | 3 +-- src/sage/categories/homset.py | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 624b26e6809..439f8734bcf 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2073,13 +2073,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()) @@ -2095,7 +2100,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)) @@ -3103,6 +3108,9 @@ def _with_axiom(self, axiom): """ Return the category obtained by adding an axiom to ``self``. + As with :meth:`Category._with_axiom`, using this method directly is + not idiomatic. + .. NOTE:: This is just an optimization of @@ -3112,7 +3120,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: diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index 014ac68d369..1b6648d36b5 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -2274,7 +2274,6 @@ def _repr_object_names_static(category, axioms): # need not repeat it here. See the example with # Sets().Finite().Subquotients() or Monoids() continue - base_category = base_category._with_axiom(axiom) if axiom == "WithBasis": result = result.replace(" over ", " with basis over ", 1) elif axiom == "Connected" and "graded " in result: @@ -2293,7 +2292,7 @@ def _repr_object_names_static(category, axioms): # Without the space at the end to handle Homsets().Endset() result = result.replace("homsets", "endsets", 1) elif axiom == "FinitelyGeneratedAsMagma" and \ - not base_category.is_subcategory(AdditiveMagmas()): + not base_category._with_axiom(axiom).is_subcategory(AdditiveMagmas()): result = "finitely generated " + result elif axiom == "FinitelyGeneratedAsLambdaBracketAlgebra": result = "finitely generated " + result diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 1b05cca4a52..4cc58966bd3 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -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 From d2405533c9830c33d665dd06b01309f12fe176bd Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:11:40 +0700 Subject: [PATCH 07/26] Better printing of debug.test_category_graph failure --- src/sage/categories/category.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 439f8734bcf..a9f3bbf1130 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2840,8 +2840,16 @@ def _make_named_class(self, name, method_provider, cache=False, **options): new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options) old_cls = self._make_named_class_cache[key] if old_cls.mro()[1:] != new_cls.mro()[1:]: + last_category = self._make_named_class_last_category_cache[key] print(f"Categories with same _make_named_class_key has different MRO: {self._all_super_categories=}", - [(i, a, b) for i, (a, b) in enumerate(zip(old_cls.mro()[1:], new_cls.mro()[1:])) if a!=b]) + f"{last_category=} {last_category._all_super_categories=}", + # List of mismatching Python classes in the MRO + [(i, a, b) for i, (a, b) in enumerate(zip(old_cls.mro()[1:], new_cls.mro()[1:])) if a!=b], + # List of mismatching categories (unlike the above, it's natural for the following to + # have many items since ``VectorSpaces(QQ).parent_class is VectorSpaces(QQ.category()).parent_class`` + [(i, a, b) for i, (a, b) in enumerate(zip( + last_category._all_super_categories[1:], self._all_super_categories[1:])) if a!=b], + ) try: return self._make_named_class_cache[key] except KeyError: @@ -2855,6 +2863,11 @@ def _make_named_class(self, name, method_provider, cache=False, **options): # throw result away and use cached value return self._make_named_class_cache[key] self._make_named_class_cache[key] = result + if debug.test_category_graph: + if not hasattr(CategoryWithParameters, '_make_named_class_last_category_cache'): + CategoryWithParameters._make_named_class_last_category_cache = {} + # if C._make_named_class(name) was called, then _make_named_class_last_category_cache[key] = C + self._make_named_class_last_category_cache[key] = self return result @abstract_method From a611e34814f92b43f2973b6186a1e69dff92b023 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:36:07 +0700 Subject: [PATCH 08/26] Hope it works --- src/sage/categories/category.py | 25 ++++++++++++- src/sage/categories/category_types.py | 1 + src/sage/categories/homsets.py | 17 ++++++++- src/sage/misc/cachefunc.pyx | 54 ++++++++++++++++++++------- src/sage/structure/debug_options.pxd | 1 + src/sage/structure/debug_options.pyx | 2 + 6 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index a9f3bbf1130..0e7c0609fd7 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -1394,8 +1394,6 @@ 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. - EXAMPLES:: sage: C = HopfAlgebrasWithBasis(QQ) @@ -2729,6 +2727,29 @@ class CategoryWithParameters(Category): sage: C1.parent_class is C3.parent_class False + .. NOTE:: + + 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: 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 + + This is because the Python MRO must match the supercategory hierarchy. + See :meth:`Category._test_category_graph`. + .. automethod:: Category._make_named_class """ diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index 01455011c40..f0b8ac8463b 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -246,6 +246,7 @@ def _make_named_class_key(self, name): sage: Algebras(Fields())._make_named_class_key('morphism_class') Category of fields """ + # return getattr(self.__base, name) # adapted from JoinCategory._make_named_class_key. Will this work? if isinstance(self.__base, Category): return self.__base return self.__base.category() diff --git a/src/sage/categories/homsets.py b/src/sage/categories/homsets.py index d930e5a3fd2..4b06e0d9c5c 100644 --- a/src/sage/categories/homsets.py +++ b/src/sage/categories/homsets.py @@ -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" @@ -155,6 +155,19 @@ 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` + + TODO add more documentation. + """ + return getattr(self.base_category(), name) # adapted from JoinCategory._make_named_class_key. Will this work? + class HomsetsOf(HomsetsCategory): """ diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index df4fa8d4457..7b344589a84 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -441,6 +441,7 @@ from inspect import isfunction from sage.misc.weak_dict cimport CachedWeakValueDictionary from sage.misc.decorators import decorator_keywords +from sage.structure.debug_options cimport debug cdef frozenset special_method_names = frozenset( ['__abs__', '__add__', @@ -1679,6 +1680,9 @@ class CachedMethodPickle(): return getattr(CM, s) +cdef object _COMPUTING = object() + + cdef class CachedMethodCaller(CachedFunction): """ Utility class that is used by :class:`CachedMethod` to bind a @@ -1950,16 +1954,28 @@ cdef class CachedMethodCaller(CachedFunction): k = self.get_key_args_kwds(args, kwds) cdef dict cache = self.cache - try: + if debug.test_nonrecursive_cachefunc: try: - return cache[k] - except TypeError: # k is not hashable - k = dict_key(k) - return cache[k] - except KeyError: - w = self._instance_call(*args, **kwds) - cache[k] = w - return w + try: + hash(k) + except TypeError: + k = dict_key(k) + result = cache[k] + if result is _COMPUTING: + raise RuntimeError("Recursive call to cached method with identical argument not supported") + return result + except KeyError: + assert k not in cache + cache[k] = _COMPUTING + try: + w = self._instance_call(*args, **kwds) + cache[k] = w + except: + del cache[k] + raise + return w + else: + raise NotImplementedError def cached(self, *args, **kwds): """ @@ -2313,10 +2329,22 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): sage: I.gens() is I.gens() True """ - if self.cache is None: - f = self.f - self.cache = f(self._instance) - return self.cache + if debug.test_nonrecursive_cachefunc: + if self.cache is _COMPUTING: + raise RuntimeError("Recursive call to cached method with no argument not supported") + if self.cache is None: + f = self.f + self.cache = _COMPUTING + try: + self.cache = f(self._instance) + except: + self.cache = None + raise + if self.cache is None: + raise RuntimeError("Ineffective cache because function returns None") + return self.cache + else: + raise NotImplementedError def set_cache(self, value): """ diff --git a/src/sage/structure/debug_options.pxd b/src/sage/structure/debug_options.pxd index 13745d44bb2..4935011668d 100644 --- a/src/sage/structure/debug_options.pxd +++ b/src/sage/structure/debug_options.pxd @@ -3,5 +3,6 @@ cdef class DebugOptions_class: cdef public bint unique_parent_warnings cdef public bint refine_category_hash_check cdef public bint test_category_graph + cdef public bint test_nonrecursive_cachefunc cdef DebugOptions_class debug diff --git a/src/sage/structure/debug_options.pyx b/src/sage/structure/debug_options.pyx index 105edc5c46f..a41e84bf6a2 100644 --- a/src/sage/structure/debug_options.pyx +++ b/src/sage/structure/debug_options.pyx @@ -39,6 +39,7 @@ cdef class DebugOptions_class: self.unique_parent_warnings = False self.refine_category_hash_check = False self.test_category_graph = True + self.test_nonrecursive_cachefunc = True def enable_extra_debugging_during_doctest(self): """ @@ -46,6 +47,7 @@ cdef class DebugOptions_class: """ self.refine_category_hash_check = True self.test_category_graph = True + self.test_nonrecursive_cachefunc = True cdef DebugOptions_class debug = DebugOptions_class() From cd602d2cb2be442713f8e91104adc482c6d1c282 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:41:29 +0700 Subject: [PATCH 09/26] Unrelated bugfix --- src/sage/combinat/integer_lists/invlex.pyx | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/integer_lists/invlex.pyx b/src/sage/combinat/integer_lists/invlex.pyx index 6bef85031ce..466089f335e 100644 --- a/src/sage/combinat/integer_lists/invlex.pyx +++ b/src/sage/combinat/integer_lists/invlex.pyx @@ -860,10 +860,13 @@ If you know what you are doing, you can set check=False to skip this warning.""" OUTPUT: - ``None`` if this method finds a proof that there + ``True`` if this method finds a proof that there exists an upper bound on the length. Otherwise a :exc:`ValueError` is raised. + Note that :func:`cached_method` does not work with methods + returning ``None``, so ``True`` is returned instead. + EXAMPLES:: sage: L = IntegerListsLex(4, max_length=4) @@ -1002,20 +1005,20 @@ If you know what you are doing, you can set check=False to skip this warning.""" """ # Trivial cases if self.max_length < Infinity: - return + return True if self.max_sum < self.min_sum: - return + return True if self.min_slope > self.max_slope: - return + return True if self.max_slope < 0: - return + return True if self.ceiling.limit() < self.floor.limit(): - return + return True if self.ceiling.limit() == 0: # This assumes no trailing zeroes - return + return True if self.min_slope > 0 and self.ceiling.limit() < Infinity: - return + return True # Compute a lower bound on the sum of floor(i) for i=1 to infinity if self.floor.limit() > 0 or self.min_slope > 0: @@ -1028,10 +1031,10 @@ If you know what you are doing, you can set check=False to skip this warning.""" floor_sum_lower_bound = Infinity if self.max_sum < floor_sum_lower_bound: - return + return True if self.max_sum == floor_sum_lower_bound and self.max_sum < Infinity: # This assumes no trailing zeroes - return + return True # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 if ( self.max_slope == 0 and @@ -1039,13 +1042,13 @@ If you know what you are doing, you can set check=False to skip this warning.""" (self.ceiling.limit_start() < Infinity and any(self.ceiling(i) == 0 for i in range(self.ceiling.limit_start()+1))) ) ): - return + return True limit_start = max(self.ceiling.limit_start(), self.floor.limit_start()) if limit_start < Infinity: for i in range(limit_start+1): if self.ceiling(i) < self.floor(i): - return + return True raise ValueError("could not prove that the specified constraints yield a finite set") From 131714e9144d0390ade62082e2eab658c4fbc47a Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:44:37 +0700 Subject: [PATCH 10/26] Too many cached function with no argument returning None, cannot raise error --- src/sage/misc/cachefunc.pyx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 7b344589a84..e5d0ae38b75 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -2340,8 +2340,6 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): except: self.cache = None raise - if self.cache is None: - raise RuntimeError("Ineffective cache because function returns None") return self.cache else: raise NotImplementedError From a4f9de688af42333dda94c7e0b758a0ee0f2ab00 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:01:59 +0700 Subject: [PATCH 11/26] Band-aid --- src/sage/symbolic/ring.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 96f3a445e9c..a1481ec50e3 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -43,7 +43,7 @@ from sage.symbolic.expression cimport ( new_Expression_symbol, ) -from sage.categories.commutative_rings import CommutativeRings +from sage.categories.fields import Fields from sage.structure.element cimport Element, Expression from sage.structure.parent cimport Parent from sage.categories.morphism cimport Morphism @@ -86,7 +86,7 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): """ if base_ring is None: base_ring = self - Parent.__init__(self, base_ring, category=CommutativeRings()) + Parent.__init__(self, base_ring, category=Fields()) self._populate_coercion_lists_(convert_method_name='_symbolic_') self.symbols = {} From 67c2486894d3cfaae9751a73f9d6cd5a98a18b86 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:31:53 +0700 Subject: [PATCH 12/26] Fix tests --- src/sage/combinat/integer_lists/invlex.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/combinat/integer_lists/invlex.pyx b/src/sage/combinat/integer_lists/invlex.pyx index 466089f335e..bfb4c8dc3d7 100644 --- a/src/sage/combinat/integer_lists/invlex.pyx +++ b/src/sage/combinat/integer_lists/invlex.pyx @@ -871,6 +871,7 @@ If you know what you are doing, you can set check=False to skip this warning.""" sage: L = IntegerListsLex(4, max_length=4) sage: L._check_finiteness() + True The following example is infinite:: From ff90f0c818cf2457d2c0d9924f81568d73a9056e Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:06:34 +0700 Subject: [PATCH 13/26] Remove debug feature and loosen up the check --- src/sage/categories/category.py | 20 +++++++++----------- src/sage/misc/cachefunc.pyx | 16 ++++++++++++++-- src/sage/structure/debug_options.pyx | 4 ++-- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 0e7c0609fd7..1dd8c0763b1 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2858,19 +2858,17 @@ def _make_named_class(self, name, method_provider, cache=False, **options): cls = cls.__base__ key = (cls, name, self._make_named_class_key(name)) if debug.test_category_graph and key in self._make_named_class_cache: - new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options) old_cls = self._make_named_class_cache[key] - if old_cls.mro()[1:] != new_cls.mro()[1:]: - last_category = self._make_named_class_last_category_cache[key] + last_category = self._make_named_class_last_category_cache[key] + # new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options) + # if old_cls.mro()[1:] != new_cls.mro()[1:]: + # ^ cannot do the above because :meth:`_make_named_class` may refine the category of the ring thus modifies key + mismatch = [(i, a, b) + for i, (a, b) in enumerate(zip(self._all_super_categories[1:], last_category._all_super_categories[1:])) + if getattr(a, name) != getattr(b, name)] + if mismatch: print(f"Categories with same _make_named_class_key has different MRO: {self._all_super_categories=}", - f"{last_category=} {last_category._all_super_categories=}", - # List of mismatching Python classes in the MRO - [(i, a, b) for i, (a, b) in enumerate(zip(old_cls.mro()[1:], new_cls.mro()[1:])) if a!=b], - # List of mismatching categories (unlike the above, it's natural for the following to - # have many items since ``VectorSpaces(QQ).parent_class is VectorSpaces(QQ.category()).parent_class`` - [(i, a, b) for i, (a, b) in enumerate(zip( - last_category._all_super_categories[1:], self._all_super_categories[1:])) if a!=b], - ) + f"{last_category=} {last_category._all_super_categories=} {mismatch=}") try: return self._make_named_class_cache[key] except KeyError: diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index e5d0ae38b75..3dbc6c5073a 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -1975,7 +1975,16 @@ cdef class CachedMethodCaller(CachedFunction): raise return w else: - raise NotImplementedError + try: + try: + return cache[k] + except TypeError: # k is not hashable + k = dict_key(k) + return cache[k] + except KeyError: + w = self._instance_call(*args, **kwds) + cache[k] = w + return w def cached(self, *args, **kwds): """ @@ -2342,7 +2351,10 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): raise return self.cache else: - raise NotImplementedError + if self.cache is None: + f = self.f + self.cache = f(self._instance) + return self.cache def set_cache(self, value): """ diff --git a/src/sage/structure/debug_options.pyx b/src/sage/structure/debug_options.pyx index a41e84bf6a2..10081424ae2 100644 --- a/src/sage/structure/debug_options.pyx +++ b/src/sage/structure/debug_options.pyx @@ -39,7 +39,7 @@ cdef class DebugOptions_class: self.unique_parent_warnings = False self.refine_category_hash_check = False self.test_category_graph = True - self.test_nonrecursive_cachefunc = True + self.test_nonrecursive_cachefunc = False def enable_extra_debugging_during_doctest(self): """ @@ -47,7 +47,7 @@ cdef class DebugOptions_class: """ self.refine_category_hash_check = True self.test_category_graph = True - self.test_nonrecursive_cachefunc = True + self.test_nonrecursive_cachefunc = False cdef DebugOptions_class debug = DebugOptions_class() From 6f35e056a7b59ec2a769c83fc6c002f5a1cb58b7 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:55:16 +0700 Subject: [PATCH 14/26] Unbreak string representation of category_with_axiom --- src/sage/categories/category_with_axiom.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index 1b6648d36b5..19143c519c8 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -2267,13 +2267,17 @@ def _repr_object_names_static(category, axioms): result = super(CategoryWithAxiom, base_category)._repr_object_names() else: result = base_category._repr_object_names() - for axiom in reversed(axioms): + for i, axiom in enumerate(reversed(axioms)): # TODO: find a more generic way to handle the special cases below if axiom in base_category.axioms(): # If the base category already has this axiom, we # need not repeat it here. See the example with # Sets().Finite().Subquotients() or Monoids() continue + if i != len(axioms) - 1 or axiom == "FinitelyGeneratedAsMagma": + # in the last iteration, base_category is no longer used except for FinitelyGeneratedAsMagma + # so as an optimization we don't need to compute this + base_category = base_category._with_axiom(axiom) if axiom == "WithBasis": result = result.replace(" over ", " with basis over ", 1) elif axiom == "Connected" and "graded " in result: @@ -2292,7 +2296,7 @@ def _repr_object_names_static(category, axioms): # Without the space at the end to handle Homsets().Endset() result = result.replace("homsets", "endsets", 1) elif axiom == "FinitelyGeneratedAsMagma" and \ - not base_category._with_axiom(axiom).is_subcategory(AdditiveMagmas()): + not base_category.is_subcategory(AdditiveMagmas()): result = "finitely generated " + result elif axiom == "FinitelyGeneratedAsLambdaBracketAlgebra": result = "finitely generated " + result From 1b4dbcac79708a3ff3cca54f7b3fbaed05a6e8a0 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:08:41 +0700 Subject: [PATCH 15/26] Revert a test, revert default category of SR to CommutativeRings() --- src/sage/categories/category.py | 21 +++++++++++++++++---- src/sage/symbolic/ring.pyx | 3 ++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 1dd8c0763b1..2382ff86ea6 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -104,6 +104,7 @@ import inspect from warnings import warn +from itertools import zip_longest from sage.misc.abstract_method import abstract_method, abstract_methods_of_class from sage.misc.cachefunc import cached_method, cached_function from sage.misc.c3_controlled import _cmp_key, _cmp_key_named, C3_sorted_merge @@ -2856,19 +2857,31 @@ def _make_named_class(self, name, method_provider, cache=False, **options): cls = self.__class__ if isinstance(cls, DynamicMetaclass): cls = cls.__base__ + if debug.test_category_graph and hasattr(self, "_Category_over_base__base"): + previous_base = self._Category_over_base__base + if hasattr(previous_base, "category"): + previous_base_category = previous_base.category() key = (cls, name, self._make_named_class_key(name)) - if debug.test_category_graph and key in self._make_named_class_cache: + if False and debug.test_category_graph and key in self._make_named_class_cache: + key2 = (cls, name, self._make_named_class_key(name)) + assert key == key2 old_cls = self._make_named_class_cache[key] last_category = self._make_named_class_last_category_cache[key] # new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options) # if old_cls.mro()[1:] != new_cls.mro()[1:]: # ^ cannot do the above because :meth:`_make_named_class` may refine the category of the ring thus modifies key + # mismatch = [(i, a, b) + # for i, (a, b) in enumerate(zip(self._all_super_categories[1:], last_category._all_super_categories[1:])) + # if getattr(a, name) != getattr(b, name)] + # ^ cannot do the above because :meth:`getattr(a, name)` may also refine the category (Heisenbug) mismatch = [(i, a, b) - for i, (a, b) in enumerate(zip(self._all_super_categories[1:], last_category._all_super_categories[1:])) - if getattr(a, name) != getattr(b, name)] + for i, (a, b) in enumerate(zip_longest(self._all_super_categories[1:], last_category._all_super_categories[1:])) + if a is None or b is None or (name in a.__dict__ and name in b.__dict__ and a.__dict__[name] != b.__dict__[name])] if mismatch: + key3 = (cls, name, self._make_named_class_key(name)) print(f"Categories with same _make_named_class_key has different MRO: {self._all_super_categories=}", - f"{last_category=} {last_category._all_super_categories=} {mismatch=}") + f"{last_category=} {last_category._all_super_categories=} {mismatch=}" + + (" (probably Heisenbug)" if key3 != key else "")) try: return self._make_named_class_cache[key] except KeyError: diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index a1481ec50e3..6f3ec6121be 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -44,6 +44,7 @@ from sage.symbolic.expression cimport ( ) from sage.categories.fields import Fields +from sage.categories.commutative_rings import CommutativeRings from sage.structure.element cimport Element, Expression from sage.structure.parent cimport Parent from sage.categories.morphism cimport Morphism @@ -86,7 +87,7 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): """ if base_ring is None: base_ring = self - Parent.__init__(self, base_ring, category=Fields()) + Parent.__init__(self, base_ring, category=CommutativeRings()) self._populate_coercion_lists_(convert_method_name='_symbolic_') self.symbols = {} From 2f49994669cfd2b5567fa1650fb022d559a6c4f5 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:05:41 +0700 Subject: [PATCH 16/26] Remove debug.test_nonrecursive_cachefunc --- src/sage/misc/cachefunc.pyx | 61 ++++++++----------------------------- 1 file changed, 13 insertions(+), 48 deletions(-) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 3dbc6c5073a..7b10174d2f9 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -441,7 +441,6 @@ from inspect import isfunction from sage.misc.weak_dict cimport CachedWeakValueDictionary from sage.misc.decorators import decorator_keywords -from sage.structure.debug_options cimport debug cdef frozenset special_method_names = frozenset( ['__abs__', '__add__', @@ -1954,37 +1953,16 @@ cdef class CachedMethodCaller(CachedFunction): k = self.get_key_args_kwds(args, kwds) cdef dict cache = self.cache - if debug.test_nonrecursive_cachefunc: - try: - try: - hash(k) - except TypeError: - k = dict_key(k) - result = cache[k] - if result is _COMPUTING: - raise RuntimeError("Recursive call to cached method with identical argument not supported") - return result - except KeyError: - assert k not in cache - cache[k] = _COMPUTING - try: - w = self._instance_call(*args, **kwds) - cache[k] = w - except: - del cache[k] - raise - return w - else: + try: try: - try: - return cache[k] - except TypeError: # k is not hashable - k = dict_key(k) - return cache[k] - except KeyError: - w = self._instance_call(*args, **kwds) - cache[k] = w - return w + return cache[k] + except TypeError: # k is not hashable + k = dict_key(k) + return cache[k] + except KeyError: + w = self._instance_call(*args, **kwds) + cache[k] = w + return w def cached(self, *args, **kwds): """ @@ -2338,23 +2316,10 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): sage: I.gens() is I.gens() True """ - if debug.test_nonrecursive_cachefunc: - if self.cache is _COMPUTING: - raise RuntimeError("Recursive call to cached method with no argument not supported") - if self.cache is None: - f = self.f - self.cache = _COMPUTING - try: - self.cache = f(self._instance) - except: - self.cache = None - raise - return self.cache - else: - if self.cache is None: - f = self.f - self.cache = f(self._instance) - return self.cache + if self.cache is None: + f = self.f + self.cache = f(self._instance) + return self.cache def set_cache(self, value): """ From 64197493e2d2d87815d71674237e2158fb51cb6c Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:10:27 +0700 Subject: [PATCH 17/26] Consistently use getattr([category], name) for _make_named_class_key --- src/sage/categories/bimodules.py | 4 +- src/sage/categories/category_types.py | 5 +- .../covariant_functorial_construction.py | 3 ++ src/sage/categories/filtered_modules.py | 51 ------------------- 4 files changed, 6 insertions(+), 57 deletions(-) diff --git a/src/sage/categories/bimodules.py b/src/sage/categories/bimodules.py index 5658cd5e514..797eda1a779 100644 --- a/src/sage/categories/bimodules.py +++ b/src/sage/categories/bimodules.py @@ -102,8 +102,8 @@ def _make_named_class_key(self, name): sage: Bimodules(Fields(), Rings())._make_named_class_key('element_class') (Category of fields, Category of rings) """ - return (self._left_base_ring if isinstance(self._left_base_ring, Category) else self._left_base_ring.category(), - self._right_base_ring if isinstance(self._right_base_ring, Category) else self._right_base_ring.category()) + return (getattr(self._left_base_ring if isinstance(self._left_base_ring, Category) else self._left_base_ring.category(), name), + getattr(self._right_base_ring if isinstance(self._right_base_ring, Category) else self._right_base_ring.category(), name)) @classmethod def an_instance(cls): diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index f0b8ac8463b..617d1e776e6 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -246,10 +246,7 @@ def _make_named_class_key(self, name): sage: Algebras(Fields())._make_named_class_key('morphism_class') Category of fields """ - # return getattr(self.__base, name) # adapted from JoinCategory._make_named_class_key. Will this work? - if isinstance(self.__base, Category): - return self.__base - return self.__base.category() + return getattr(self.__base if isinstance(self.__base, Category) else self.__base.category(), name) @classmethod def an_instance(cls): diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index aee0d22a6af..e03202f6695 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -457,6 +457,9 @@ def base_category(self): """ return self._base_category + def _make_named_class_key(self, name): + return getattr(self._base_category, name) + def extra_super_categories(self): """ Return the extra super categories of a construction category. diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py index a3572de40bd..4d4f94d9f6d 100644 --- a/src/sage/categories/filtered_modules.py +++ b/src/sage/categories/filtered_modules.py @@ -67,57 +67,6 @@ 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') - (, - Join of Category of Dedekind domains and Category of euclidean domains and Category of noetherian rings and Category of infinite enumerated sets and Category of metric spaces) - - 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') - (, - Join of Category of number fields and Category of quotient fields and Category of metric spaces) - sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') - (, - Join of Category of number fields and Category of quotient fields and Category of metric spaces) - sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != - ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) - sage: VectorSpacesQQ.Filtered().parent_class - - sage: ModulesQQ.Filtered().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 (type(self._base_category).__base__, super()._make_named_class_key(name)) - class FilteredModules(FilteredModulesCategory): r""" From ba7d5d95955279c68b00a0306fe87526762bfac7 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:12:21 +0700 Subject: [PATCH 18/26] Add back the docstring --- .../covariant_functorial_construction.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index e03202f6695..579e6c9910b 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -458,6 +458,54 @@ def base_category(self): return self._base_category def _make_named_class_key(self, name): + """ + 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') + (, + Join of Category of Dedekind domains and Category of euclidean domains and Category of noetherian rings and Category of infinite enumerated sets and Category of metric spaces) + + 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') + (, + Join of Category of number fields and Category of quotient fields and Category of metric spaces) + sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') + (, + Join of Category of number fields and Category of quotient fields and Category of metric spaces) + sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != + ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) + sage: VectorSpacesQQ.Filtered().parent_class + + sage: ModulesQQ.Filtered().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) def extra_super_categories(self): From 84bda7225f77f5491493d43f25b2b679d567c9f4 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:16:21 +0700 Subject: [PATCH 19/26] Mark a test as long time --- src/sage/groups/perm_gps/permgroup_named.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index d7dd1bdb2c4..bd57a0f32c6 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -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] - ....: 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() From 5933e4761b461b339b1c6037c6f12253d89fcc36 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:25:52 +0700 Subject: [PATCH 20/26] Move make_named_class_key back to filteredmodules --- .../covariant_functorial_construction.py | 51 ------------------- src/sage/categories/filtered_modules.py | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index 579e6c9910b..aee0d22a6af 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -457,57 +457,6 @@ def base_category(self): """ return self._base_category - def _make_named_class_key(self, name): - """ - 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') - (, - Join of Category of Dedekind domains and Category of euclidean domains and Category of noetherian rings and Category of infinite enumerated sets and Category of metric spaces) - - 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') - (, - Join of Category of number fields and Category of quotient fields and Category of metric spaces) - sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') - (, - Join of Category of number fields and Category of quotient fields and Category of metric spaces) - sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != - ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) - sage: VectorSpacesQQ.Filtered().parent_class - - sage: ModulesQQ.Filtered().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) - def extra_super_categories(self): """ Return the extra super categories of a construction category. diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py index 4d4f94d9f6d..323d7c0e326 100644 --- a/src/sage/categories/filtered_modules.py +++ b/src/sage/categories/filtered_modules.py @@ -67,6 +67,57 @@ 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') + (, + Join of Category of Dedekind domains and Category of euclidean domains and Category of noetherian rings and Category of infinite enumerated sets and Category of metric spaces) + + 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') + (, + Join of Category of number fields and Category of quotient fields and Category of metric spaces) + sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') + (, + Join of Category of number fields and Category of quotient fields and Category of metric spaces) + sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != + ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) + sage: VectorSpacesQQ.Filtered().parent_class + + sage: ModulesQQ.Filtered().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""" From 939d6090d2834f2dea053462da521d7044a8e039 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:26:42 +0700 Subject: [PATCH 21/26] Fix tests accordingly --- src/sage/categories/bimodules.py | 27 +++++++-------------- src/sage/categories/category.py | 32 ++++++------------------- src/sage/categories/category_types.py | 18 ++++---------- src/sage/categories/filtered_modules.py | 9 +++---- 4 files changed, 23 insertions(+), 63 deletions(-) diff --git a/src/sage/categories/bimodules.py b/src/sage/categories/bimodules.py index 797eda1a779..f10f2c1021c 100644 --- a/src/sage/categories/bimodules.py +++ b/src/sage/categories/bimodules.py @@ -75,32 +75,21 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Bimodules(QQ,ZZ)._make_named_class_key('parent_class') - (Join of Category of number fields - and Category of quotient fields - and Category of metric spaces, - Join of Category of Dedekind domains - and Category of euclidean domains - and Category of noetherian rings - and Category of infinite enumerated sets - and Category of metric spaces) + (, + ) sage: Bimodules(Fields(), ZZ)._make_named_class_key('element_class') - (Category of fields, - Join of Category of Dedekind domains - and Category of euclidean domains - and Category of noetherian rings - and Category of infinite enumerated sets - and Category of metric spaces) + (, + ) sage: Bimodules(QQ, Rings())._make_named_class_key('element_class') - (Join of Category of number fields - and Category of quotient fields - and Category of metric spaces, - Category of rings) + (, + ) sage: Bimodules(Fields(), Rings())._make_named_class_key('element_class') - (Category of fields, Category of rings) + (, + ) """ return (getattr(self._left_base_ring if isinstance(self._left_base_ring, Category) else self._left_base_ring.category(), name), getattr(self._right_base_ring if isinstance(self._right_base_ring, Category) else self._right_base_ring.category(), name)) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 2382ff86ea6..8e842eb7404 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2924,24 +2924,14 @@ def _make_named_class_key(self, name): The parent class of an algebra depends only on the category of the base ring:: sage: Algebras(ZZ)._make_named_class_key("parent_class") - Join of Category of Dedekind domains - and Category of euclidean domains - and Category of noetherian rings - and Category of infinite enumerated sets - and Category of metric spaces + The morphism class of a bimodule depends only on the category of the left and right base rings:: sage: Bimodules(QQ, ZZ)._make_named_class_key("morphism_class") - (Join of Category of number fields - and Category of quotient fields - and Category of metric spaces, - Join of Category of Dedekind domains - and Category of euclidean domains - and Category of noetherian rings - and Category of infinite enumerated sets - and Category of metric spaces) + (, + ) The element class of a join category depends only on the element class of its super categories:: @@ -3065,21 +3055,13 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ)._make_named_class_key('element_class') - Join of Category of Dedekind domains - and Category of euclidean domains - and Category of noetherian rings - and Category of infinite enumerated sets - and Category of metric spaces + sage: Modules(QQ)._make_named_class_key('parent_class') - Join of Category of number fields - and Category of quotient fields - and Category of metric spaces + sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') - Category of schemes + sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Join of Category of number fields - and Category of quotient fields - and Category of metric spaces + """ return tuple(getattr(cat, name) for cat in self._super_categories) diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index 617d1e776e6..add68abae4a 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -228,23 +228,15 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ)._make_named_class_key('element_class') - Join of Category of Dedekind domains - and Category of euclidean domains - and Category of noetherian rings - and Category of infinite enumerated sets - and Category of metric spaces + sage: Modules(QQ)._make_named_class_key('parent_class') - Join of Category of number fields - and Category of quotient fields - and Category of metric spaces + sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') - Category of schemes + sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Join of Category of number fields - and Category of quotient fields - and Category of metric spaces + sage: Algebras(Fields())._make_named_class_key('morphism_class') - Category of fields + """ return getattr(self.__base if isinstance(self.__base, Category) else self.__base.category(), name) diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py index 323d7c0e326..17dcf1fbdca 100644 --- a/src/sage/categories/filtered_modules.py +++ b/src/sage/categories/filtered_modules.py @@ -78,8 +78,7 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ).Filtered()._make_named_class_key('element_class') - (, - Join of Category of Dedekind domains and Category of euclidean domains and Category of noetherian rings and Category of infinite enumerated sets and Category of metric spaces) + Note that we cannot simply return the base as in :meth:`.Category_over_base._make_named_class_key` because of the following @@ -97,11 +96,9 @@ def _make_named_class_key(self, name): sage: ModulesQQ.Filtered() Category of filtered modules over Rational Field sage: VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') - (, - Join of Category of number fields and Category of quotient fields and Category of metric spaces) + sage: ModulesQQ.Filtered()._make_named_class_key('parent_class') - (, - Join of Category of number fields and Category of quotient fields and Category of metric spaces) + sage: assert (VectorSpacesQQ.Filtered()._make_named_class_key('parent_class') != ....: ModulesQQ.Filtered()._make_named_class_key('parent_class')) sage: VectorSpacesQQ.Filtered().parent_class From 182f33f460dd242f3df2e227498c5d861b9d053c Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:35:46 +0700 Subject: [PATCH 22/26] Oops, forget one check (cause all the trouble?) --- src/sage/categories/category.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 8e842eb7404..a09547e8177 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -1636,8 +1636,13 @@ def subcategory_class(self): sage: type(cls) """ - return self._make_named_class('subcategory_class', 'SubcategoryMethods', - cache=False, picklable=False) + subcategory_class = self._make_named_class('subcategory_class', 'SubcategoryMethods', + cache=False, picklable=False) + if debug.test_category_graph: + # see also _test_category_graph() + if subcategory_class.mro()[1:] != [C.subcategory_class for C in self._all_super_categories_proper] + [object]: + print(f"Category graph does not match with Python MRO: {subcategory_class=} {subcategory_class.mro()=} {self._all_super_categories=}") + return subcategory_class @lazy_attribute def parent_class(self): From eee57f2001cde252945040ac42156aa75c25bc54 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 09:23:19 +0700 Subject: [PATCH 23/26] Revert "Remove debug.test_nonrecursive_cachefunc" This reverts commit 2f49994669cfd2b5567fa1650fb022d559a6c4f5. --- src/sage/misc/cachefunc.pyx | 61 +++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 7b10174d2f9..3dbc6c5073a 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -441,6 +441,7 @@ from inspect import isfunction from sage.misc.weak_dict cimport CachedWeakValueDictionary from sage.misc.decorators import decorator_keywords +from sage.structure.debug_options cimport debug cdef frozenset special_method_names = frozenset( ['__abs__', '__add__', @@ -1953,16 +1954,37 @@ cdef class CachedMethodCaller(CachedFunction): k = self.get_key_args_kwds(args, kwds) cdef dict cache = self.cache - try: + if debug.test_nonrecursive_cachefunc: try: - return cache[k] - except TypeError: # k is not hashable - k = dict_key(k) - return cache[k] - except KeyError: - w = self._instance_call(*args, **kwds) - cache[k] = w - return w + try: + hash(k) + except TypeError: + k = dict_key(k) + result = cache[k] + if result is _COMPUTING: + raise RuntimeError("Recursive call to cached method with identical argument not supported") + return result + except KeyError: + assert k not in cache + cache[k] = _COMPUTING + try: + w = self._instance_call(*args, **kwds) + cache[k] = w + except: + del cache[k] + raise + return w + else: + try: + try: + return cache[k] + except TypeError: # k is not hashable + k = dict_key(k) + return cache[k] + except KeyError: + w = self._instance_call(*args, **kwds) + cache[k] = w + return w def cached(self, *args, **kwds): """ @@ -2316,10 +2338,23 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): sage: I.gens() is I.gens() True """ - if self.cache is None: - f = self.f - self.cache = f(self._instance) - return self.cache + if debug.test_nonrecursive_cachefunc: + if self.cache is _COMPUTING: + raise RuntimeError("Recursive call to cached method with no argument not supported") + if self.cache is None: + f = self.f + self.cache = _COMPUTING + try: + self.cache = f(self._instance) + except: + self.cache = None + raise + return self.cache + else: + if self.cache is None: + f = self.f + self.cache = f(self._instance) + return self.cache def set_cache(self, value): """ From 3781c6525194be31ecf9a0a10b6d8d06d026e0e8 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:14:24 +0700 Subject: [PATCH 24/26] Give up --- src/sage/categories/category.py | 24 +++++++++++++++------- src/sage/categories/category_with_axiom.py | 10 ++++++--- src/sage/structure/debug_options.pyx | 6 +++--- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index a09547e8177..29b8c2a0de1 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -1640,8 +1640,18 @@ def subcategory_class(self): cache=False, picklable=False) if debug.test_category_graph: # see also _test_category_graph() - if subcategory_class.mro()[1:] != [C.subcategory_class for C in self._all_super_categories_proper] + [object]: - print(f"Category graph does not match with Python MRO: {subcategory_class=} {subcategory_class.mro()=} {self._all_super_categories=}") + previous_all_super_categories = self.__dict__.get("_all_super_categories", None) + for C in self._all_super_categories_proper: + assert "subcategory_class" in C.__dict__ # avoid Heisenbug (relies on implementation detail of lazy_attribute) + mismatch = [(i, a, b) + for i, (a, b) in enumerate(zip_longest( + subcategory_class.mro()[1:], + [C.subcategory_class for C in self._all_super_categories_proper] + [object] + )) + if a is None or b is None or a != b] + if mismatch: + print(f"Category graph does not match with Python MRO: {subcategory_class=} {subcategory_class.mro()=} {mismatch=}") + # we cannot even print self._all_super_categories here otherwise there's a risk of infinite recursion return subcategory_class @lazy_attribute @@ -2867,9 +2877,10 @@ def _make_named_class(self, name, method_provider, cache=False, **options): if hasattr(previous_base, "category"): previous_base_category = previous_base.category() key = (cls, name, self._make_named_class_key(name)) - if False and debug.test_category_graph and key in self._make_named_class_cache: + if debug.test_category_graph: key2 = (cls, name, self._make_named_class_key(name)) assert key == key2 + if False and debug.test_category_graph and key in self._make_named_class_cache: old_cls = self._make_named_class_cache[key] last_category = self._make_named_class_last_category_cache[key] # new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options) @@ -2895,10 +2906,9 @@ def _make_named_class(self, name, method_provider, cache=False, **options): cache=cache, **options) # the object in the parameter may have had its category refined, which modifies the key, needs to recompute # (problem with mutable objects) - key = (cls, name, self._make_named_class_key(name)) - if key in self._make_named_class_cache: - # throw result away and use cached value - return self._make_named_class_cache[key] + if key != (cls, name, self._make_named_class_key(name)): + # throw result away and recompute + return self._make_named_class(name, method_provider, cache=cache, **options) self._make_named_class_cache[key] = result if debug.test_category_graph: if not hasattr(CategoryWithParameters, '_make_named_class_last_category_cache'): diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index 19143c519c8..5e93392af87 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -2269,7 +2269,7 @@ def _repr_object_names_static(category, axioms): result = base_category._repr_object_names() for i, axiom in enumerate(reversed(axioms)): # TODO: find a more generic way to handle the special cases below - if axiom in base_category.axioms(): + if base_category is not None and axiom in base_category.axioms(): # If the base category already has this axiom, we # need not repeat it here. See the example with # Sets().Finite().Subquotients() or Monoids() @@ -2277,7 +2277,11 @@ def _repr_object_names_static(category, axioms): if i != len(axioms) - 1 or axiom == "FinitelyGeneratedAsMagma": # in the last iteration, base_category is no longer used except for FinitelyGeneratedAsMagma # so as an optimization we don't need to compute this - base_category = base_category._with_axiom(axiom) + if "subcategory_class" not in base_category.__dict__: + base_category = None + result = " " + result + if base_category is not None: + base_category = base_category._with_axiom(axiom) if axiom == "WithBasis": result = result.replace(" over ", " with basis over ", 1) elif axiom == "Connected" and "graded " in result: @@ -2296,7 +2300,7 @@ def _repr_object_names_static(category, axioms): # Without the space at the end to handle Homsets().Endset() result = result.replace("homsets", "endsets", 1) elif axiom == "FinitelyGeneratedAsMagma" and \ - not base_category.is_subcategory(AdditiveMagmas()): + base_category is not None and not base_category.is_subcategory(AdditiveMagmas()): result = "finitely generated " + result elif axiom == "FinitelyGeneratedAsLambdaBracketAlgebra": result = "finitely generated " + result diff --git a/src/sage/structure/debug_options.pyx b/src/sage/structure/debug_options.pyx index 10081424ae2..b09fa396adf 100644 --- a/src/sage/structure/debug_options.pyx +++ b/src/sage/structure/debug_options.pyx @@ -38,15 +38,15 @@ cdef class DebugOptions_class: """ self.unique_parent_warnings = False self.refine_category_hash_check = False - self.test_category_graph = True - self.test_nonrecursive_cachefunc = False + self.test_category_graph = False + self.test_nonrecursive_cachefunc = True def enable_extra_debugging_during_doctest(self): """ Function that is called before doctest to enable extra debugging options. """ self.refine_category_hash_check = True - self.test_category_graph = True + self.test_category_graph = False self.test_nonrecursive_cachefunc = False From fbaa1a78244896599ef327296bc23a0f43a884d1 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:25:56 +0700 Subject: [PATCH 25/26] Try again? --- src/sage/categories/category.py | 6 ++++++ src/sage/structure/debug_options.pyx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 29b8c2a0de1..91f0b63a1a2 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -1636,6 +1636,8 @@ def subcategory_class(self): sage: type(cls) """ + if debug.test_category_graph: + _ = self._all_super_categories subcategory_class = self._make_named_class('subcategory_class', 'SubcategoryMethods', cache=False, picklable=False) if debug.test_category_graph: @@ -1697,6 +1699,8 @@ def parent_class(self): :class:`~sage.categories.category_types.Category_over_base` and :class:`sage.categories.category.JoinCategory`. """ + if debug.test_category_graph: + _ = self._all_super_categories parent_class = self._make_named_class('parent_class', 'ParentMethods') if debug.test_category_graph: # see also _test_category_graph() @@ -1749,6 +1753,8 @@ def element_class(self): .. SEEALSO:: :meth:`parent_class` """ + if debug.test_category_graph: + _ = self._all_super_categories element_class = self._make_named_class('element_class', 'ElementMethods') if debug.test_category_graph: # see also _test_category_graph() diff --git a/src/sage/structure/debug_options.pyx b/src/sage/structure/debug_options.pyx index b09fa396adf..83fac45181e 100644 --- a/src/sage/structure/debug_options.pyx +++ b/src/sage/structure/debug_options.pyx @@ -46,7 +46,7 @@ cdef class DebugOptions_class: Function that is called before doctest to enable extra debugging options. """ self.refine_category_hash_check = True - self.test_category_graph = False + self.test_category_graph = True self.test_nonrecursive_cachefunc = False From 97229b19a92499b414d5dcbbd5ebdf67c7706cbb Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:28:09 +0700 Subject: [PATCH 26/26] Re-enable a test --- src/sage/categories/category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 91f0b63a1a2..1b9713ac44f 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2886,7 +2886,7 @@ def _make_named_class(self, name, method_provider, cache=False, **options): if debug.test_category_graph: key2 = (cls, name, self._make_named_class_key(name)) assert key == key2 - if False and debug.test_category_graph and key in self._make_named_class_cache: + if debug.test_category_graph and key in self._make_named_class_cache: old_cls = self._make_named_class_cache[key] last_category = self._make_named_class_last_category_cache[key] # new_cls = Category._make_named_class(self, name, method_provider, cache=cache, **options)