Ticket #11115: trac11115_element_with_cache.patch

File trac11115_element_with_cache.patch, 16.0 KB (added by SimonKing, 3 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        """