Ticket #11115: trac11115-ElementWithCache.patch

File trac11115-ElementWithCache.patch, 16.0 KB (added by SimonKing, 6 years ago)

Support cached methods for elements only if attributes can be assigned. Provide a subclass of Element that does fully support cached methods and lazy attributes

  • sage/misc/cachefunc.pyx

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1305527781 -7200
    # Node ID 63979466255e0f03a97d4f4209b022e8d2bf47ac
    # Parent  d60cb5b380ced1223ffbd3a3706b38814a7daee5
    #11115: lazy atributes and cached methods for elements without attribute assignment
    
    diff --git a/sage/misc/cachefunc.pyx b/sage/misc/cachefunc.pyx
    a b  
    9797    sage: cython('\n'.join(cython_code))
    9898    sage: C = MyCategory()
    9999
    100 To make cacheing work on the elements, we need to provide it with
    101 a public attribute ``__cached_methods``, as in the second class below::
     100In order to keep the memory footprint of elements small, it was
     101decided to not support the same freedom of using cached methods
     102for elements: If an instance of a class derived from
     103:class:`~sage.structure.element.Element` does not allow attribute
     104assignment, then a cached method inherited from the category of
     105its parent will break, as in the class ``MyBrokenElement`` below.
    102106
    103     sage: cython_code = ["from sage.structure.element cimport Element",
     107However, there is a class :class:`~sage.structure.element.ElementWithCachedMethod`
     108that has generally a slower attribute access, but fully supports
     109cached methods. We remark, however, that cached methods are
     110*much* faster if attribute access works. So, we expect that
     111:class:`~sage.structure.element.ElementWithCachedMethod` will
     112hardly by used.
     113::
     114
     115    sage: cython_code = ["from sage.structure.element cimport Element, ElementWithCachedMethod",
    104116    ... "cdef class MyBrokenElement(Element):",
    105117    ... "    cdef public object x",
    106118    ... "    def __init__(self,P,x):",
    107119    ... "        self.x=x",
    108120    ... "        Element.__init__(self,P)",
    109121    ... "    def __neg__(self):",
     122    ... "        return MyBrokenElement(self.parent(),-self.x)",
     123    ... "    def _repr_(self):",
     124    ... "        return '<%s>'%self.x",
     125    ... "    def __hash__(self):",
     126    ... "        return hash(self.x)",
     127    ... "    def __cmp__(left, right):",
     128    ... "        return (<Element>left)._cmp(right)",
     129    ... "    def __richcmp__(left, right, op):",
     130    ... "        return (<Element>left)._richcmp(right,op)",
     131    ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
     132    ... "        return cmp(left.x,right.x)",
     133    ... "    def raw_test(self):",
     134    ... "        return -self",
     135    ... "cdef class MyElement(ElementWithCachedMethod):",
     136    ... "    cdef public object x",
     137    ... "    def __init__(self,P,x):",
     138    ... "        self.x=x",
     139    ... "        ElementWithCachedMethod.__init__(self,P)",
     140    ... "    def __neg__(self):",
    110141    ... "        return MyElement(self.parent(),-self.x)",
    111142    ... "    def _repr_(self):",
    112143    ... "        return '<%s>'%self.x",
    113144    ... "    def __hash__(self):",
    114145    ... "        return hash(self.x)",
    115     ... "    def __cmp__(self,other):",
    116     ... "        if isinstance(other, MyBrokenElement):",
    117     ... "            return cmp(self.x,other.x)",
    118     ... "        return -1",
     146    ... "    def __cmp__(left, right):",
     147    ... "        return (<Element>left)._cmp(right)",
     148    ... "    def __richcmp__(left, right, op):",
     149    ... "        return (<Element>left)._richcmp(right,op)",
     150    ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
     151    ... "        return cmp(left.x,right.x)",
    119152    ... "    def raw_test(self):",
    120153    ... "        return -self",
    121     ... "cdef class MyElement(MyBrokenElement):",
    122     ... "    cdef public dict __cached_methods",
    123154    ... "from sage.structure.parent cimport Parent",
    124155    ... "cdef class MyParent(Parent):",
    125156    ... "    Element = MyElement"]
     
    139170    sage: P.invert(e) is P.invert(e)
    140171    True
    141172
    142 The cached methods inherited by the element with ``__cached_method`` works::
     173The cached methods inherited by ``MyElement`` works::
    143174
    144175    sage: e.element_cache_test()
    145176    <-5>
  • sage/misc/lazy_attribute.py

    diff --git a/sage/misc/lazy_attribute.py b/sage/misc/lazy_attribute.py
    a b  
    518518        """
    519519        if a is None: # when doing cls.x for cls a class and x a lazy attribute
    520520            return self
    521         if hasattr(a.__class__, '__cached_methods'):
     521        try:
     522            # __cached_methods is supposed to be a public Cython attribute.
     523            # Apparently, these are *not* subject to name mangling.
    522524            CM = getattr(a, '__cached_methods')
    523525            if CM is None:
    524                 CM = a.__cached_methods = {}
    525         else:
     526                CM = {}
     527                setattr(a, '__cached_methods', CM)
     528        except AttributeError,msg:
    526529            CM = None
    527530        if CM is not None:
    528531            try:
  • sage/structure/element.pxd

    diff --git a/sage/structure/element.pxd b/sage/structure/element.pxd
    a b  
    2323    cpdef _act_on_(self, x, bint self_on_left)
    2424    cpdef _acted_upon_(self, x, bint self_on_left)
    2525   
    26    
     26cdef class ElementWithCachedMethod(Element):
     27    cdef public dict __cached_methods
     28
    2729cdef class ModuleElement(Element)       # forward declaration
    2830
    2931cdef class RingElement(ModuleElement)   # forward declaration
  • sage/structure/element.pyx

    diff --git a/sage/structure/element.pyx b/sage/structure/element.pyx
    a b  
    5050
    5151            MonoidElement
    5252                MultiplicativeGroupElement
     53        ElementWithCachedMethod
    5354
    5455
    5556How to Define a New Element Class
     
    944945    """
    945946    return IS_INSTANCE(x, ModuleElement)
    946947
     948cdef class ElementWithCachedMethod(Element):
     949    r"""
     950    An element class that fully supports cached methods.
     951
     952    NOTE:
     953
     954    The :class:`~sage.misc.cachefunc.cached_method` decorator provides
     955    a convenient way to automatically cache the result of a computation.
     956    Since trac ticket #11115, the cached method decorator applied to a
     957    method without optional arguments is faster than a hand-written cache
     958    in Python, and a cached method without any arguments (except ``self``)
     959    is actually faster than a Python method that does nothing more but
     960    to return ``1``. A cached method can also be inherited from the parent
     961    or element class of a category.
     962
     963    However, this holds true only if attribute assignment is supported.
     964    If you write an extension class in Cython that does not accept attribute
     965    assignment then a cached method inherited from the category will be
     966    slower (for :class:`~sage.structure.parent.Parent`) or the cache would
     967    even break (for :class:`Element`).
     968
     969    This class should be used if you write an element class, can not provide
     970    it with attribute assignment, but want that it inherits a cached method
     971    from the category. Under these conditions, your class should inherit
     972    from this class rather than :class:`Element`. Then, the cache will work,
     973    but certainly slower than with attribute assignment. Lazy attributes
     974    work as well.
     975
     976    EXAMPLE:
     977
     978    We define three element extension classes. The first inherits from
     979    :class:`Element`, the second from this class, and the third simply
     980    is a Python class. We also define a parent class and, in Python, a
     981    category whose element and parent classes define cached methods.
     982    ::
     983
     984        sage: cython_code = ["from sage.structure.element cimport Element, ElementWithCachedMethod",
     985        ... "cdef class MyBrokenElement(Element):",
     986        ... "    cdef public object x",
     987        ... "    def __init__(self,P,x):",
     988        ... "        self.x=x",
     989        ... "        Element.__init__(self,P)",
     990        ... "    def __neg__(self):",
     991        ... "        return MyBrokenElement(self.parent(),-self.x)",
     992        ... "    def _repr_(self):",
     993        ... "        return '<%s>'%self.x",
     994        ... "    def __hash__(self):",
     995        ... "        return hash(self.x)",
     996        ... "    def __cmp__(left, right):",
     997        ... "        return (<Element>left)._cmp(right)",
     998        ... "    def __richcmp__(left, right, op):",
     999        ... "        return (<Element>left)._richcmp(right,op)",
     1000        ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
     1001        ... "        return cmp(left.x,right.x)",
     1002        ... "    def raw_test(self):",
     1003        ... "        return -self",
     1004        ... "cdef class MyElement(ElementWithCachedMethod):",
     1005        ... "    cdef public object x",
     1006        ... "    def __init__(self,P,x):",
     1007        ... "        self.x=x",
     1008        ... "        Element.__init__(self,P)",
     1009        ... "    def __neg__(self):",
     1010        ... "        return MyElement(self.parent(),-self.x)",
     1011        ... "    def _repr_(self):",
     1012        ... "        return '<%s>'%self.x",
     1013        ... "    def __hash__(self):",
     1014        ... "        return hash(self.x)",
     1015        ... "    def __cmp__(left, right):",
     1016        ... "        return (<Element>left)._cmp(right)",
     1017        ... "    def __richcmp__(left, right, op):",
     1018        ... "        return (<Element>left)._richcmp(right,op)",
     1019        ... "    cdef int _cmp_c_impl(left, Element right) except -2:",
     1020        ... "        return cmp(left.x,right.x)",
     1021        ... "    def raw_test(self):",
     1022        ... "        return -self",
     1023        ... "class MyPythonElement(MyBrokenElement): pass",
     1024        ... "from sage.structure.parent cimport Parent",
     1025        ... "cdef class MyParent(Parent):",
     1026        ... "    Element = MyElement"]
     1027        sage: cython('\n'.join(cython_code))
     1028        sage: cython_code = ["from sage.all import cached_method, cached_in_parent_method, Category",
     1029        ... "class MyCategory(Category):",
     1030        ... "    @cached_method",
     1031        ... "    def super_categories(self):",
     1032        ... "        return [Objects()]",
     1033        ... "    class ElementMethods:",
     1034        ... "        @cached_method",
     1035        ... "        def element_cache_test(self):",
     1036        ... "            return -self",
     1037        ... "        @cached_in_parent_method",
     1038        ... "        def element_via_parent_test(self):",
     1039        ... "            return -self",
     1040        ... "    class ParentMethods:",
     1041        ... "        @cached_method",
     1042        ... "        def one(self):",
     1043        ... "            return self.element_class(self,1)",
     1044        ... "        @cached_method",
     1045        ... "        def invert(self, x):",
     1046        ... "            return -x"]
     1047        sage: cython('\n'.join(cython_code))
     1048        sage: C = MyCategory()
     1049        sage: P = MyParent(category=C)
     1050        sage: ebroken = MyBrokenElement(P,5)
     1051        sage: e = MyElement(P,5)
     1052
     1053    The cached methods inherited by ``MyElement`` works::
     1054
     1055        sage: e.element_cache_test()
     1056        <-5>
     1057        sage: e.element_cache_test() is e.element_cache_test()
     1058        True
     1059        sage: e.element_via_parent_test()
     1060        <-5>
     1061        sage: e.element_via_parent_test() is e.element_via_parent_test()
     1062        True
     1063
     1064    The other element class can only inherit a
     1065    ``cached_in_parent_method``, since the cache is stored in the
     1066    parent. In fact, equal elements share the cache, even if they are
     1067    of different types::
     1068
     1069        sage: e == ebroken
     1070        True
     1071        sage: type(e) == type(ebroken)
     1072        False
     1073        sage: ebroken.element_via_parent_test() is e.element_via_parent_test()
     1074        True
     1075
     1076    However, the cache of the other inherited method breaks, although the method
     1077    as such works::
     1078
     1079        sage: ebroken.element_cache_test()
     1080        <-5>
     1081        sage: ebroken.element_cache_test() is ebroken.element_cache_test()
     1082        False
     1083
     1084    Since ``e`` and ``ebroken`` share the cache, when we empty it for one element
     1085    it is empty for the other as well::
     1086
     1087        sage: b = ebroken.element_via_parent_test()
     1088        sage: e.element_via_parent_test.clear_cache()
     1089        sage: b is ebroken.element_via_parent_test()
     1090        False
     1091
     1092    Note that the cache only breaks for elements that do no allow attribute assignment.
     1093    A Python version of ``MyBrokenElement`` therefore allows for cached methods::
     1094
     1095        sage: epython = MyPythonElement(P,5)
     1096        sage: epython.element_cache_test()
     1097        <-5>
     1098        sage: epython.element_cache_test() is epython.element_cache_test()
     1099        True
     1100
     1101    """
     1102    def __getattr__(self, name):
     1103        """
     1104        This getattr method ensures that cached methods and lazy attributes
     1105        can be inherited from the element class of a category.
     1106
     1107        NOTE:
     1108
     1109        The use of cached methods is demonstrated in the main doc string of
     1110        this class. Here, we demonstrate lazy attributes.
     1111
     1112        EXAMPLE::
     1113
     1114            sage: cython_code = ["from sage.structure.element cimport ElementWithCachedMethod",
     1115            ... "cdef class MyElement(ElementWithCachedMethod):",
     1116            ... "    cdef public object x",
     1117            ... "    def __init__(self,P,x):",
     1118            ... "        self.x=x",
     1119            ... "        ElementWithCachedMethod.__init__(self,P)",
     1120            ... "    def _repr_(self):",
     1121            ... "        return '<%s>'%self.x",
     1122            ... "from sage.structure.parent cimport Parent",
     1123            ... "cdef class MyParent(Parent):",
     1124            ... "    Element = MyElement",
     1125            ... "from sage.all import cached_method, lazy_attribute, Category",
     1126            ... "class MyCategory(Category):",
     1127            ... "    @cached_method",
     1128            ... "    def super_categories(self):",
     1129            ... "        return [Objects()]",
     1130            ... "    class ElementMethods:",
     1131            ... "        @lazy_attribute",
     1132            ... "        def my_lazy_attr(self):",
     1133            ... "            return 'lazy attribute of <%d>'%self.x"]
     1134            sage: cython('\n'.join(cython_code))
     1135            sage: C = MyCategory()
     1136            sage: P = MyParent(category=C)
     1137            sage: e = MyElement(P,5)
     1138            sage: e.my_lazy_attr
     1139            'lazy attribute of <5>'
     1140            sage: e.my_lazy_attr is e.my_lazy_attr
     1141            True
     1142
     1143        """
     1144        if name.startswith('__') and not name.endswith('_'):
     1145            raise AttributeError, AttributeErrorMessage(self, name)
     1146        try:
     1147            return self.__cached_methods[name]
     1148        except KeyError:
     1149            attr = getattr_from_other_class(self,
     1150                                        self._parent.category().element_class,
     1151                                        name)
     1152            self.__cached_methods[name] = attr
     1153            return attr
     1154        except TypeError:
     1155            attr = getattr_from_other_class(self,
     1156                                        self._parent.category().element_class,
     1157                                        name)
     1158            self.__cached_methods = {name : attr}
     1159            return attr
    9471160
    9481161cdef class ModuleElement(Element):
    9491162    """
    9501163    Generic element of a module.
    9511164    """
    952    
     1165
    9531166    ##################################################
    9541167    # Addition
    9551168    ##################################################   
  • sage/structure/parent.pyx

    diff --git a/sage/structure/parent.pyx b/sage/structure/parent.pyx
    a b  
    783783            AttributeError: 'sage.structure.parent.Parent' object has no attribute '__foo'
    784784
    785785        """
     786        if (name.startswith('__') and not name.endswith('_')) or self._category is None:
     787            raise AttributeError, AttributeErrorMessage(self, name)
    786788        try:
    787789            return self.__cached_methods[name]
    788         except (KeyError,TypeError):
    789             pass
    790         if (name.startswith('__') and not name.endswith('_')) or self._category is None:
    791             raise AttributeError, AttributeErrorMessage(self, name)
    792         if self.__cached_methods is None:
    793             self.__cached_methods  = {}
    794         attr = getattr_from_other_class(self, self._category.parent_class, name)
    795         self.__cached_methods[name] = attr
    796         return attr
     790        except KeyError:
     791            attr = getattr_from_other_class(self, self._category.parent_class, name)
     792            self.__cached_methods[name] = attr
     793            return attr
     794        except TypeError:
     795            attr = getattr_from_other_class(self, self._category.parent_class, name)
     796            self.__cached_methods = {name:attr}
     797            return attr
    797798
    798799    def __dir__(self):
    799800        """