# Ticket #11943: trac11943_mro_for_all_super_categories_lazy_hook-review-nt.patch

File trac11943_mro_for_all_super_categories_lazy_hook-review-nt.patch, 31.6 KB (added by nthiery, 9 years ago)
• ## sage/categories/algebras.py

# HG changeset patch
# User Nicolas M. Thiery <nthiery@users.sf.net>
# Date 1334173460 -7200
# Node ID 48836a0aebf9f214ee9fa6c809e75f79f6b45c60
# Parent  725cc00e833e7cec1a489409701e54334dd543b5
imported patch trac11943_mro_for_all_super_categories_lazy_hook-review-nt.patch

diff --git a/sage/categories/algebras.py b/sage/categories/algebras.py
 a from sage.categories.cartesian_product i from sage.categories.dual import DualObjectsCategory from sage.categories.tensor import TensorProductsCategory, tensor from sage.categories.morphism import SetMorphism from sage.categories.modules import Modules from sage.categories.rings import Rings from sage.categories.modules import Modules from sage.misc.cachefunc import cached_method from sage.structure.sage_object import have_same_parent
• ## sage/categories/category.py

diff --git a/sage/categories/category.py b/sage/categories/category.py
 a A parent P is in a category C if from sage.misc.abstract_method import abstract_method, abstract_methods_of_class from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method, cached_function from sage.misc.c3 import C3_algorithm, C3_algorithm_set from sage.misc.c3 import C3_algorithm from sage.misc.unknown import Unknown #from sage.misc.misc import attrcall from sage.structure.sage_object import SageObject class Category(UniqueRepresentation, Sag #         """ #         return hash(self.__category) # Any reason not to use id? def _subcategory_hook_(self, C): def _subcategory_hook_(self, category): """ Quick subcategory check. INPUT: C - a category - category -- a category OUTPUT: - True, if C is a subcategory of self. - False, if C is not a subcategory of self. - NotImplemented, if a quick check was not enough to determine whether C is a subcategory of self or not. - True, if category is a subcategory of self. - False, if category is not a subcategory of self. - Unknown, if a quick check was not enough to determine whether category is a subcategory of self or not. NOTE: The aim of this method is to offer a framework to add cheap tests for subcategories. When doing category.is_subcategory(self) (note the reverse order of self and category), this method is usually called first.  Only if it returns Unknown, :meth:is_subcategory will build the list of super categories of category. This method need not to handle the case where category is self; this is the first test that is done in :meth:is_subcategory. This default implementation tests whether the parent class of C is a subclass of the parent class of self. of category is a subclass of the parent class of self. Currently (as of trac ticket #11900) this is a complete subcategory test. But this will change with trac ticket #11935. The aim of this method is to offer a framework to add a cheap test for subcategories. When doing C.is_subcategory(self) (note the reverse order of self and C), this method is usually called first. Only if it returns NotImplemented, :meth:is_subcategory will determine the list of super categories of C. It is not needed to deal with the case that C is self, because this is the first test that is done in :meth:is_subcategory. OUTPUT: - True, if it is certain that C is a subcategory of self. - False, if it is certain that C is no subcategory of self. - NotImplemented, if the question whether C is a subcategory of self shall be answered by studying the list of super-categories of C. EXAMPLE:: sage: Rings()._subcategory_hook_(Rings()) True """ # return True if C is a proper sub-category of self; # It is not needed to consider the case "C is self". # return False if C is clearly not a sub-category # of self. # return NotImplemented otherwise. return issubclass(C.parent_class, self.parent_class) return issubclass(category.parent_class, self.parent_class) def __contains__(self, x): """ class Category(UniqueRepresentation, Sag @abstract_method def super_categories(self): """ Returns the immediate super categories of self Returns the *immediate* super categories of self Every category should implement this method. EXAMPLES:: class Category(UniqueRepresentation, Sag [Category of monoids] sage: Objects().super_categories() [] .. note:: Mathematically speaking, the order of the super categories should be irrelevant. However, in practice, this order influences the result of :meth:all_super_categories, and accordingly of the method resolution order for parent and element classes. Namely, since ticket 11943, Sage uses the same C3 algorithm for determining the order on the list of *all* super categories as Python is using for the method resolution order of new style classes. .. note:: Whenever speed matters, developers are advised to use the lazy attribute :meth:_super_categories instead of calling this method. """ @cached_method def _all_super_categories_raw(self): """ Return all super categories of this category, without removing duplicates. @lazy_attribute def _all_super_categories(self): r""" All the super categories of this category, including this category. TEST:: Since :trac:11943, the order of super categories is determined by Python's method resolution order C3 algorithm. sage: Rngs()._all_super_categories_raw() [Category of commutative additive groups, .. seealso:: :meth:all_super_categories .. note:: this attribute is likely to eventually become a tuple. EXAMPLES:: sage: C = Rings(); C Category of rings sage: C._all_super_categories [Category of rings, Category of rngs, Category of commutative additive groups, Category of semirings, Category of commutative additive monoids, Category of commutative additive semigroups, Category of additive magmas, Category of sets, Category of sets with partial maps, Category of objects, Category of additive magmas, Category of monoids, Category of semigroups, Category of magmas, Category of sets, Category of sets with partial maps, Category of objects] """ return C3_algorithm(self,'_super_categories','_all_super_categories',False) @lazy_attribute def _all_super_categories_proper(self): r""" All the proper super categories of this category. Since :trac:11943, the order of super categories is determined by Python's method resolution order C3 algorithm. .. seealso:: :meth:all_super_categories .. note:: this attribute is likely to eventually become a tuple. EXAMPLES:: sage: C = Rings(); C Category of rings sage: C._all_super_categories_proper [Category of rngs, Category of commutative additive groups, Category of semirings, Category of commutative additive monoids, Category of commutative additive semigroups, Category of additive magmas, Category of monoids, Category of semigroups, Category of magmas, Category of sets, Category of sets with partial maps, Category of objects] """ all_categories = [] for cat in self.super_categories(): all_categories.append(cat) all_categories.extend(cat._all_super_categories_raw()) return all_categories return self._all_super_categories[1:] @cached_method def all_super_categories(self, proper = False): r""" Returns a linear extension (topological sort) of all the (proper) super categories of this category, and cache the result. @lazy_attribute def _set_of_super_categories(self): """ The frozen set of all proper super categories of this category. .. note:: this is used for speeding up category containment tests. .. seealso:: :meth:all_super_categories EXAMPLES:: sage: Groups()._set_of_super_categories frozenset([...]) sage: sorted(Groups()._set_of_super_categories, key=repr) [Category of magmas, Category of monoids, Category of objects, Category of semigroups, Category of sets, Category of sets with partial maps] TESTS:: sage: C = HopfAlgebrasWithBasis(GF(7)) sage: C._set_of_super_categories == frozenset(C._all_super_categories_proper) True """ return frozenset(self._all_super_categories_proper) def all_super_categories(self, proper=False): """ Returns the list of all super categories of this category. INPUT: - proper: a boolean; defaults to False.  Whether to exclude this category. - proper -- a boolean (default: False); whether to exclude this category. FIXME: Since :trac:11943, the order of super categories is determined by Python's method resolution order C3 algorithm. - make sure that this is compatible with the python algorithm for method resolution and make it O(n+m) .. note:: Whenever speed matters, the developers are advised to use instead the lazy attributes :meth:_all_super_categories, :meth:_all_super_categories_proper, or :meth:_set_of_all_super_categories, as appropriate. Simply because lazy attributes are much faster than any method. EXAMPLES:: class Category(UniqueRepresentation, Sag Category of sets, Category of sets with partial maps, Category of objects] """ return C3_algorithm(self,'_super_categories','_all_super_categories',False) @lazy_attribute def _all_super_categories_proper(self): r""" All proper super categories of this category. NOTE: By trac ticket #11943, the order of super categories is determined by the C3 algorithm. EXAMPLES:: sage: C = GradedHopfAlgebrasWithBasis(QQ).abstract_category(); C Category of abstract graded hopf algebras with basis over Rational Field sage: C._all_super_categories_proper [Category of graded hopf algebras over Rational Field, Category of graded bialgebras over Rational Field, Category of graded algebras over Rational Field, Category of graded coalgebras over Rational Field, Category of graded modules over Rational Field, Category of hopf algebras over Rational Field, Category of bialgebras over Rational Field, Category of algebras over Rational Field, ...] """ return C3_algorithm(self,'_super_categories','_all_super_categories',True) @lazy_attribute def _set_of_super_categories(self): """ The frozen set of all proper super categories of this category. NOTE: This is used for a speed-up in category containment tests. TEST:: sage: C = HopfAlgebrasWithBasis(GF(7)) sage: C._set_of_super_categories == frozenset(C._all_super_categories_proper) True """ try: return frozenset(self.__dict__['_all_super_categories_proper']) except KeyError: return frozenset(C3_algorithm_set(self,'_super_categories','_all_super_categories',True)) def all_super_categories(self, proper=False): """ Returns the list of all super categories of this category. NOTE: By trac ticket #11943, the order of super categories is determined by the :func:~sage.misc.c3.C3_algorithm. INPUT: - proper: a boolean; defaults to False.  Whether to exclude this category. NOTE: This method exists for convenience. However, the developers are advised to use the lazy attribute _all_super_categories or _all_super_categories_proper when asking for the super categories - simply because a lazy attribute is much faster than any method. EXAMPLE:: sage: Sets().all_super_categories() [Category of sets, Category of sets with partial maps, Category of objects] class Category(UniqueRepresentation, Sag @lazy_attribute def _super_categories(self): """ Returns the immediate super categories of this category. The immediate super categories of this category. NOTE: This lazy attributes caches the result of the mandatory method :meth:super_categories for speed. For implementing this lazy attribute, you must provide a method :meth:super_categories. However, the developers are advised to use the lazy attribute _super_categories when asking for the super categories - simply because a lazy attribute is much faster than any method. Whenever speed matters, developers are advised to use this lazy attribute rather than calling :meth:super_categories. .. note:: this attribute is likely to eventually become a tuple. EXAMPLES:: sage: Rings()._super_categories [Category of rngs, Category of semirings] """ return self.super_categories() def super_categories(self): """ Implement this method to provide the *immediate* super categories. NOTE: The order of immediate super categories matters in exactly the same way as the order of base classes of a Python class matters. In fact, by trac ticket #11943, the category framework of Sage uses the same algorithm for determining the order on the list of *all* super categories that Python is using for the method resolution order of new style classes. NOTE: In order to avoid overhead, the developers are advised to use the lazy attribute _super_categories. While super_categories() must be provided, but it should not be called, for the sake of speed. """ raise NotImplementedError def _test_category_graph(self, **options): """ Verify that Python's method resolution coincides with the category graph. Check that the category graph matches with Python's method resolution order NOTE: .. note:: By trac ticket #11943, the list of categories returned by :meth:all_super_categories is supposed to correspond to the method resolution order of the parent or element classes. This method verifies it. By :trac:11943, the list of categories returned by :meth:all_super_categories is supposed to match with the method resolution order of the parent and element classes. This method checks this. TODO: .. todo:: currently, this won't work for hom categories. Currently, it won't work for hom categories. EXAMPLE:: EXAMPLES:: sage: C = HopfAlgebrasWithBasis(QQ) sage: C.parent_class.mro() == [X.parent_class for X in C._all_super_categories] + [object] class Category(UniqueRepresentation, Sag if c is self: return True subcat_hook = c._subcategory_hook_(self) if subcat_hook is NotImplemented: if subcat_hook is Unknown: return c in self._set_of_super_categories return subcat_hook def category_graph(categories = None): categories = [ cat.an_instance() for cat in sage.categories.all.__dict__.values() if isinstance(cat, type) and issubclass(cat, Category) and cat not in abstract_classes_for_categories ] cats = set() for category in categories: for cat in category._all_super_categories: for cat in category.all_super_categories(): cats.add(cat) categories = cats def category_graph(categories = None): for cat in categories: g.add_vertex(cat._repr_object_names()) for source in categories: for target in source._super_categories: for target in source.super_categories(): g.add_edge([source._repr_object_names(), target._repr_object_names()]) return g class JoinCategory(Category): """ return self._super_categories def _subcategory_hook_(self, C): def _subcategory_hook_(self, category): """ Tells whether this join category contains another category as a subcategory. Returns whether category is a subcategory of this join category INPUT: C -- a category. - category -- a category. OUTPUT: .. note:: Whether C is sub-category of self or not. NOTE: C is sub-category of this join category if and only if it is sub-category for all super categories of this join category. category is a sub-category of this join category if and only if it is a sub-category of all super categories of this join category. EXAMPLE:: sage: QQ['x'].category().is_subcategory(Category.join([Rings(), VectorSpaces(QQ)]))  # indirect doctest True """ for X in self._super_categories: if not C.is_subcategory(X): return False return True return all(category.is_subcategory(X) for X in self._super_categories) def _repr_(self): """
• ## sage/categories/category_types.py

diff --git a/sage/categories/category_types.py b/sage/categories/category_types.py
 a This is placed in a separate file from c #                  http://www.gnu.org/licenses/ #***************************************************************************** from sage.misc.cachefunc import cached_method from sage.misc.latex import latex from category import Category from objects import Objects
• ## sage/categories/covariant_functorial_construction.py

diff --git a/sage/categories/covariant_functorial_construction.py b/sage/categories/covariant_functorial_construction.py
 a class CovariantConstructionCategory(Cate return Category.join([getattr(cat, cls._functor_category)(*args) for cat in category._super_categories]) def is_subcategory(self, C): """ .. todo:: doctests + explain why this method is needed """ if C is self: return True for X in self._super_categories: if X.is_subcategory(C): return True return False return any(X.is_subcategory(C) for X in self._super_categories) def __init__(self, category, *args): """ class CovariantConstructionCategory(Cate sage: from sage.categories.covariant_functorial_construction import CovariantConstructionCategory sage: class FooBars(CovariantConstructionCategory): ...       pass sage: C = FooBars(ModulesWithBasis(QQ)) ...       _functor_category = "FooBars" sage: Category.FooBars = lambda self: FooBars.category_of(self) sage: C = FooBars(ModulesWithBasis(ZZ)) sage: C Category of foo bars of modules with basis over Rational Field Category of foo bars of modules with basis over Integer Ring sage: C.base_category() Category of modules with basis over Rational Field Category of modules with basis over Integer Ring sage: latex(C) \mathbf{FooBars}(\mathbf{ModulesWithBasis}_{\Bold{Q}}) \mathbf{FooBars}(\mathbf{ModulesWithBasis}_{\Bold{Z}}) sage: import __main__; __main__.FooBars = FooBars # Fake FooBars being defined in a python module sage: TestSuite(C).run() """
• ## sage/categories/magmas.py

diff --git a/sage/categories/magmas.py b/sage/categories/magmas.py
 a class Magmas(Category_singleton): TESTS:: sage: C = Magmas() sage: TestSuite(C).run(verbose=True) running ._test_category() . . . pass running ._test_category_graph() . . . pass running ._test_not_implemented_methods() . . . pass running ._test_pickling() . . . pass sage: TestSuite(C).run() """ def super_categories(self): """
• ## sage/categories/primer.py

diff --git a/sage/categories/primer.py b/sage/categories/primer.py
 a of some other category. This determines A category *may* provide methods that can be used by all its objects, respectively by all elements of its objects. Each category *should* come with a good example, in sage.categories.examples. Each category *should* come with a good example, in :mod:sage.categories.examples. Inserting the new category into the category graph .................................................. of *all* super categories of C, by usi that is also used for the method resolution order of new-style classes in Python (see trac ticket #11943). Of course, if you know that your new category C is immediate super category of an existing category D, then you should modify Of course, if you know that your new category C is an immediate super category of some existing category D, then you should modify D's super_categories method so that C is included. Although the order between super categories should not be mathematically relevant, the order *is* relevant for inheritance of methods. Namely, a category can provide methods for all its objects and for the elements for all its objects. If P is an object in the category C and if C_1 and C_2 are both super categories of C defining some method foo, then P will use C_1's version of foo only if C_1 appears in C.all_super_categories() before C_2. The order between the super categories does influence the inheritance of methods for parents and elements. Namely, if P is an object in the category C and if C_1 and C_2 are both super categories of C defining some method foo, then P will use C_1's version of foo only if C_1 appears in C.all_super_categories() before C_2. This is an *implementation detail*: the order between super categories should not be mathematically relevant, and code should not rely on any specific order, as it it subject to later change. Also, the C3 algorithm will only be able to determine a consistent order on the list of all super categories if the orders on the different lists This gives the following order:: Category of sets with partial maps, Category of objects] When in doubt, C.is_subcategory(D) returns True if and only if D appears in C.all_super_categories(). However, creating the list of all super categories is an expensive operation that can sometimes be avoided. For example, if both C and D are categories defined over a base, but the bases differ, then they can not be subcategories of each other. Subcategory hook (advanced optimization feature) ................................................ If such a short-path is known, one can implement a method The default implementation of the method C.is_subcategory(D) is to look up whether D appears in C.all_super_categories(). However, building the list of all the super categories of C is an expensive operation that is sometimes best avoided. For example, if both C and D are categories defined over a base, but the bases differ, then one knows right away that they can not be subcategories of each other. When such a short-path is known, one can implement a method _subcategory_hook_. C.is_subcategory(D) first calls D._subcategory_hook_(C). If this returns NotImplemented, then D._subcategory_hook_(C). If this returns Unknown, then C.is_subcategory(D) tries to find D in C.all_super_categories(). Otherwise, C.is_subcategory(D) returns the result of D._subcategory_hook_(C).
• ## sage/misc/c3.pyx

diff --git a/sage/misc/c3.pyx b/sage/misc/c3.pyx
 a AUTHOR: - Simon King (2011-11): initial version. """ #***************************************************************************** #  Copyright (C) 2011    Simon King # #  Distributed under the terms of the GNU General Public License (GPL) #                  http://www.gnu.org/licenses/ #****************************************************************************** include "../ext/python.pxi" cpdef list C3_algorithm(object start, str bases, str attribute, bint proper): """ The C3 algorithm. An implementation of the C3 algorithm. NOTE: C3 is the algorithm used by Python to construct the method resolution order for new style classes involving multiple inheritance. This implementation is used to order the list of super categories of a category; see :meth:~sage.categories.category.Category.all_super_categories. By consequence, the list of super categories exactly corresponds to the method resolution order of the parent or element class of a category. This is because Python uses the same algorithm (of course in a different implementation) as method resolution order for new style classes. This implementation is used to order the list of super categories of a category; see :meth:~sage.categories.category.Category.all_super_categories. The purpose is to ensure that list of super categories matches with the method resolution order of the parent or element classes of a category. INPUT: - start (object): The returned list is built upon data - start -- an object; the returned list is built upon data provided by certain attributes of start. - bases (string): The name of an attribute of start - bases -- a string; the name of an attribute of start providing a list of objects. - attribute (string): The name of an attribute of the objects provided in getattr(start,bases). That attribute is supposed to provide a list. - attribute -- a string; the name of an attribute of the objects provided in getattr(start,bases). That attribute is supposed to provide a list. ASSUMPTIONS: cpdef list C3_algorithm(object start, st EXAMPLES: We start with an example of categories with an inconsistent base classes of a new class:: We start with some categories having an inconsistent inheritance order:: sage: class X(Category): ...    def super_categories(self): ...        return [Objects()] sage: class X(Category): ...    def super_categories(self): ...        return [Objects()] sage: class Y(Category): ...    def super_categories(self): ...        return [Objects()] sage: class A(Category): ...    def super_categories(self): ...        return [X(),Y()] ...        return [X(), Y()] sage: class B(Category): ...    def super_categories(self): ...        return [Y(),X()] ...        return [Y(), X()] sage: class Foo(Category): ...    def super_categories(self): ...       return [A(),B()] ...       return [A(), B()] sage: F = Foo() Python is not able to create a consistent method resolution order cpdef list C3_algorithm(object start, st Traceback (most recent call last): ... TypeError: Cannot create a consistent method resolution order (MRO) for bases X.parent_class, Y.parent_class order (MRO) for bases ....parent_class, ....parent_class Since the C3 algorithm is used for determining the list of all super categories (by trac ticket #11943), a similar error cpdef list C3_algorithm(object start, st sage: C = Category.join([HopfAlgebrasWithBasis(QQ), FiniteEnumeratedSets()]) sage: from sage.misc.c3 import C3_algorithm sage: C3_algorithm(C,'_super_categories','_all_super_categories',True)==C._all_super_categories_proper sage: C3_algorithm(C,'_super_categories','_all_super_categories',True) == C._all_super_categories_proper True sage: C3_algorithm(C,'_super_categories','_all_super_categories',False)==C._all_super_categories sage: C3_algorithm(C,'_super_categories','_all_super_categories',False) == C._all_super_categories True By trac ticket #11943, the following consistency tests are part cpdef list C3_algorithm(object start, st curr_set.remove(O) except IndexError: tails[i] = None i = -1 # Either we need to raise an error, or the list is done. if tails.count(None)