Ticket #715: 715_all.patch

File 715_all.patch, 54.4 KB (added by jdemeyer, 7 years ago)
  • doc/en/reference/structure.rst

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1326139789 -3600
    # Node ID de9f19a8dedc0863676041f555f746809ac45db5
    # Parent  c95c1ffa8aff73e6055bf82919815d93bf66d1a8
    Use weak references in the `TripleDict` coercion cache
    The C % operator can return negative results, thus, we use an unsigned type for the hash
    
    diff --git a/doc/en/reference/structure.rst b/doc/en/reference/structure.rst
    a b  
    99   sage/structure/parent_old
    1010   sage/structure/parent_base
    1111   sage/structure/parent_gens
     12   sage/structure/coerce_dict
    1213   sage/structure/formal_sum
    1314   sage/structure/factorization
    1415   sage/structure/element
  • sage/categories/action.pxd

    diff --git a/sage/categories/action.pxd b/sage/categories/action.pxd
    a b  
    55
    66cdef class Action(Functor):
    77    cdef G
    8     cdef S
     8    cdef US
    99    cdef bint _is_left
    1010    cdef op
     11    cdef underlying_set(self)
    1112    cpdef _call_(self, a, b)
    1213   
    1314   
  • sage/categories/action.pyx

    diff --git a/sage/categories/action.pyx b/sage/categories/action.pyx
    a b  
    11r"""
    2 Group, ring, etc. actions on objects. 
     2Group, ring, etc. actions on objects.
    33
    4 The terminology and notation used is suggestive of groups
    5 acting on sets, but this framework can be used for modules,
    6 algebras, etc.
     4The terminology and notation used is suggestive of groups acting on sets,
     5but this framework can be used for modules, algebras, etc.
    76
    8 A group action $G \times S \rightarrow S$ is a functor from $G$ to Sets. 
     7A group action $G \times S \rightarrow S$ is a functor from $G$ to Sets.
    98
    10 AUTHORS:
    11     -- Robert Bradshaw: initial version
     9.. WARNING::
     10
     11    An :class:`Action` object only keeps a weak reference to the underlying set
     12    which is acted upon. This decision was made in :trac:`715` in order to
     13    allow garbage collection within the coercion framework (this is where
     14    actions are mainly used) and avoid memory leaks.
     15
     16    ::
     17
     18        sage: from sage.categories.action import Action
     19        sage: class P: pass
     20        sage: A = Action(P(),P())
     21        sage: import gc
     22        sage: _ = gc.collect()
     23        sage: A
     24        Traceback (most recent call last):
     25        ...
     26        RuntimeError: This action acted on a set that became garbage collected
     27
     28    To avoid garbage collection of the underlying set, it is sufficient to
     29    create a strong reference to it before the action is created.
     30
     31    ::
     32
     33        sage: _ = gc.collect()
     34        sage: from sage.categories.action import Action
     35        sage: class P: pass
     36        sage: q = P()
     37        sage: A = Action(P(),q)
     38        sage: gc.collect()
     39        0
     40        sage: A
     41        Left action by <__main__.P instance at ...> on <__main__.P instance at ...>
     42
     43AUTHOR:
     44
     45- Robert Bradshaw: initial version
    1246"""
    1347
    1448#*****************************************************************************
     
    3266
    3367import homset
    3468import sage.structure.element
     69from weakref import ref
    3570
    3671include "../ext/stdsage.pxi"
    3772
     
    4883        from groupoid import Groupoid
    4984        Functor.__init__(self, Groupoid(G), category(S))
    5085        self.G = G
    51         self.S = S
     86        self.US = ref(S)
    5287        self._is_left = is_left
    5388        self.op = op
    5489       
     
    6196            if g in self.G:
    6297                return ActionEndomorphism(self, self.G(g))
    6398            elif g == self.G:
    64                 return self.S
     99                return self.underlying_set()
    65100            else:
    66101                raise TypeError, "%s not an element of %s"%(g, self.G)
    67102        elif len(args) == 2:
    68103            if self._is_left:
    69                 return self._call_(self.G(args[0]), self.S(args[1]))
     104                return self._call_(self.G(args[0]), self.underlying_set()(args[1]))
    70105            else:
    71                 return self._call_(self.S(args[0]), self.G(args[1]))
     106                return self._call_(self.underlying_set()(args[0]), self.G(args[1]))
    72107           
    73108    cpdef _call_(self, a, b):
    74109        raise NotImplementedError, "Action not implemented."
     
    91126       
    92127    def __repr__(self):
    93128        side = "Left" if self._is_left else "Right"
    94         return "%s %s by %r on %r"%(side, self._repr_name_(), self.G, self.S)
     129        return "%s %s by %r on %r"%(side, self._repr_name_(), self.G,
     130                                    self.underlying_set())
    95131       
    96132    def _repr_name_(self):
    97133        return "action"
    98134       
    99135    def actor(self):
    100136        return self.G
    101    
     137
     138    cdef underlying_set(self):
     139        """
     140        The set on which the actor acts (it is not necessarily the codomain of
     141        the action).
     142
     143        NOTE:
     144
     145        Since this is a cdef'ed method, we can only provide an indirect doctest.
     146
     147        EXAMPLES::
     148
     149            sage: P = QQ['x']
     150            sage: R = (ZZ['x'])['y']
     151            sage: A = R.get_action(P,operator.mul,True)
     152            sage: A                 # indirect doctest
     153            Right scalar multiplication by Univariate Polynomial Ring in x over
     154            Rational Field on Univariate Polynomial Ring in y over Univariate
     155            Polynomial Ring in x over Integer Ring
     156
     157        In this example, the underlying set is the ring ``R``. This is the same
     158        as the left domain, which is different from the codomain of the action::
     159
     160            sage: A.codomain()
     161            Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field
     162            sage: A.codomain() == R
     163            False
     164            sage: A.left_domain() is R
     165            True
     166
     167        By :trac:`715`, there is only a weak reference to the underlying set.
     168        Hence, the underlying set may be garbage collected, even when the
     169        action is still alive. This may result in a runtime error, as follows::
     170
     171            sage: from sage.categories.action import Action
     172            sage: class P: pass
     173            sage: p = P()
     174            sage: q = P()
     175            sage: A = Action(p,q)
     176            sage: A
     177            Left action by <__main__.P instance at ...> on <__main__.P instance at ...>
     178            sage: del q
     179            sage: import gc
     180            sage: _ = gc.collect()
     181            sage: A
     182            Traceback (most recent call last):
     183            ...
     184            RuntimeError: This action acted on a set that became garbage collected
     185
     186        """
     187        S = self.US()
     188        if S is None:
     189            raise RuntimeError, "This action acted on a set that became garbage collected"
     190        return S
     191
    102192    def codomain(self):
    103         return self.S
     193       return self.underlying_set()
    104194       
    105195    def domain(self):
    106         return self.S
     196        return self.underlying_set()
    107197       
    108198    def left_domain(self):
    109199        if self._is_left:
     
    145235            # We must be in the case that parent(~a) == parent(a)
    146236            # so we can invert in call_c code below.
    147237            if (PY_TYPE_CHECK(G, Group) and G.is_multiplicative()) or G.is_field():
    148                 Action.__init__(self, G, action.S, action._is_left)
     238                Action.__init__(self, G, action.underlying_set(), action._is_left)
    149239                self._action = action
    150240                return
    151241            else:
    152242                K = G._pseudo_fraction_field()
    153                 Action.__init__(self, K, action.S, action._is_left)
     243                Action.__init__(self, K, action.underlying_set(), action._is_left)
    154244                self._action = action
    155245                return
    156246        except (AttributeError, NotImplementedError):
     
    190280              right_precomposition = homset.Hom(right_precomposition._codomain, right).natural_map() * right_precomposition
    191281            right = right_precomposition._domain
    192282        if action._is_left:
    193             Action.__init__(self, left, action.S, 1)
     283            Action.__init__(self, left, action.underlying_set(), 1)
    194284        else:
    195             Action.__init__(self, right, action.S, 0)
     285            Action.__init__(self, right, action.underlying_set(), 0)
    196286        self._action = action
    197287        self.left_precomposition = left_precomposition
    198288        self.right_precomposition = right_precomposition
     
    230320cdef class ActionEndomorphism(Morphism):
    231321   
    232322    def __init__(self, Action action, g):
    233         Morphism.__init__(self, homset.Hom(action.S, action.S))
     323        Morphism.__init__(self, homset.Hom(action.underlying_set(),
     324                                           action.underlying_set()))
    234325        self._action = action
    235326        self._g = g
    236327       
     
    241332            return self._action._call_(x, self._g)
    242333               
    243334    def _repr_(self):
    244         return "Action of %s on %s under %s."%(self._g, self._action.S, self._action)
     335        return "Action of %s on %s under %s."%(self._g,
     336                                               self._action.underlying_set(), self._action)
    245337       
    246338    def __mul__(left, right):
    247339        cdef ActionEndomorphism left_c, right_c
  • sage/categories/functor.pxd

    diff --git a/sage/categories/functor.pxd b/sage/categories/functor.pxd
    a b  
    11from sage.structure.sage_object cimport SageObject
    22
    33cdef class Functor(SageObject):
     4    cdef __weakref__
    45    cdef object __domain
    5     cdef object __codomain
    6  No newline at end of file
     6    cdef object __codomain
  • sage/matrix/action.pyx

    diff --git a/sage/matrix/action.pyx b/sage/matrix/action.pyx
    a b  
    11"""
    2 These are the actions used by the coercion model for matrix and vector multiplications.
     2These are the actions used by the coercion model for matrix and vector
     3multiplications.
    34
    4 AUTHORS:
    5     -- Robert Bradshaw (2007-09): Initial version.
     5.. WARNING::
     6
     7    The class :class:`MatrixMulAction` and its descendants extends the class
     8    :class:`Action`. As a cosnequence objects from these classes only keep weak
     9    references to the underlying sets which are acted upon. This decision was
     10    made in :trac:`715` in order to allow garbage collection within the coercion
     11    framework, where actions are mainly used, and avoid memory leaks.
     12
     13    To ensure that the underlying set of such an object does not get garbage
     14    collected, it is sufficient to explicitely create a strong reference to it
     15    before creating the action.
     16
     17    ::
     18
     19        sage: MSQ = MatrixSpace(QQ, 2)
     20        sage: MSZ = MatrixSpace(ZZ['x'], 2)
     21        sage: A = MSQ.get_action(MSZ)
     22        sage: A
     23        Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field on Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring
     24        sage: import gc
     25        sage: _ = gc.collect()
     26        sage: A
     27        Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field on Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring
     28
     29.. NOTE::
     30
     31    The :func:`MatrixSpace` function caches the objects it creates. Therefore,
     32    the underlying set ``MSZ`` in the above example will not be garbage
     33    collected, even if it is not strongly ref'ed. Nonetheless, there is no
     34    guarantee that the set that is acted upon will always be cached in such a
     35    way, so that following the above example is good practice.
     36
     37AUTHOR:
     38
     39- Robert Bradshaw (2007-09): Initial version.
    640"""
    741
    842#*****************************************************************************
     
    3872       
    3973    def domain(self):
    4074        """
    41         EXAMPLES:
    42             sage: A = MatrixSpace(QQ, 2).get_action(MatrixSpace(ZZ['x'], 2)); A
     75        EXAMPLES:
     76
     77        By :trac:`715`, there only is a weak reference on the underlying set,
     78        so that it can be garbage collected if only the action itself is
     79        explicitly referred to. Hence, we first assign the involved matrix
     80        spaces to a variable::
     81
     82            sage: MSQ = MatrixSpace(QQ, 2)
     83            sage: MSZ = MatrixSpace(ZZ['x'], 2)
     84            sage: A = MSQ.get_action(MSZ); A
    4385            Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field on Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring
    4486            sage: A.actor()
    4587            Full MatrixSpace of 2 by 2 dense matrices over Rational Field
     
    4789            Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring
    4890            sage: A.codomain()
    4991            Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field
     92
     93        .. NOTE::
     94
     95            The :func:`MatrixSpace` function caches the object it creates.
     96            Therefore, the underlying set ``MSZ`` in the above example will not
     97            be garbage collected, even if it is not strongly ref'ed.
     98            Nonetheless, there is no guarantee that the set that is acted upon
     99            will always be cached in such a way, so that following the above
     100            example is good practice.
     101
    50102        """
    51         return self.S
     103        return self.underlying_set()
    52104
    53105
    54106cdef class MatrixMatrixAction(MatrixMulAction):
    55107    def __init__(self, G, S):
    56108        """
    57         EXAMPLES:
     109        EXAMPLES:
     110
     111        By :trac:`715`, there only is a weak reference on the underlying set,
     112        so that it can be garbage collected if only the action itself is
     113        explicitly referred to. Hence, we first assign the involved matrix
     114        spaces to a variable::
     115
    58116            sage: R.<x> = ZZ[]
     117            sage: MSR = MatrixSpace(R, 3, 3)
     118            sage: MSQ = MatrixSpace(QQ, 3, 2)
    59119            sage: from sage.matrix.action import MatrixMatrixAction
    60             sage: A = MatrixMatrixAction(MatrixSpace(R, 3, 3), MatrixSpace(QQ, 3, 2)); A
     120            sage: A = MatrixMatrixAction(MSR, MSQ); A
    61121            Left action by Full MatrixSpace of 3 by 3 dense matrices over Univariate Polynomial Ring in x over Integer Ring on Full MatrixSpace of 3 by 2 dense matrices over Rational Field
    62122            sage: A.codomain()
    63123            Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field
     
    65125            [  0   x]
    66126            [2*x 3*x]
    67127            [4*x 5*x]
     128
     129        .. NOTE::
     130
     131            The :func:`MatrixSpace` function caches the object it creates.
     132            Therefore, the underlying set ``MSZ`` in the above example will not
     133            be garbage collected, even if it is not strongly ref'ed.
     134            Nonetheless, there is no guarantee that the set that is acted upon
     135            will always be cached in such a way, so that following the above
     136            example is good practice.
     137
    68138        """
    69139        if not is_MatrixSpace(S):
    70140            raise TypeError, "Not a matrix space: %s" % S
     
    72142       
    73143    def _create_codomain(self, base):
    74144        """
    75         EXAMPLES:
     145        EXAMPLES:
     146
     147        By :trac:`715`, there only is a weak reference on the underlying set,
     148        so that it can be garbage collected if only the action itself is
     149        explicitly referred to. Hence, we first assign the involved matrix
     150        spaces to a variable::
     151
    76152            sage: from sage.matrix.action import MatrixMatrixAction
    77153            sage: R.<x> = ZZ[]
    78             sage: A = MatrixMatrixAction(MatrixSpace(R, 3, 3), MatrixSpace(QQ, 3, 2)); A
     154            sage: MSR = MatrixSpace(R, 3, 3)
     155            sage: MSQ = MatrixSpace(QQ, 3, 2)
     156            sage: A = MatrixMatrixAction(MSR, MSQ); A
    79157            Left action by Full MatrixSpace of 3 by 3 dense matrices over Univariate Polynomial Ring in x over Integer Ring on Full MatrixSpace of 3 by 2 dense matrices over Rational Field
    80158            sage: A.codomain()
    81159            Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field
     160
     161        .. NOTE::
     162
     163            The :func:`MatrixSpace` function caches the object it creates.
     164            Therefore, the underlying set ``MSZ`` in the above example will not
     165            be garbage collected, even if it is not strongly ref'ed.
     166            Nonetheless, there is no guarantee that the set that is acted upon
     167            will always be cached in such a way, so that following the above
     168            example is good practice.
     169
    82170        """
    83         if self.G.ncols() != self.S.nrows():
    84             raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),  self.S.nrows())
    85         return MatrixSpace(base, self.G.nrows(), self.S.ncols(), sparse = self.G.is_sparse() and self.S.is_sparse())
     171        if self.G.ncols() != self.underlying_set().nrows():
     172            raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),  self.underlying_set().nrows())
     173        return MatrixSpace(base, self.G.nrows(), self.underlying_set().ncols(),
     174                           sparse = self.G.is_sparse() and self.underlying_set().is_sparse())
    86175       
    87176    cpdef _call_(self, g, s):
    88177        """
    89178        EXAMPLES:
    90         Respects compatible subdivisions:
     179
     180        Respects compatible subdivisions::
     181
    91182            sage: M = matrix(5, 5, prime_range(100))
    92183            sage: M.subdivide(2,3); M
    93184            [ 2  3  5| 7 11]
     
    112203            [ 8168|11143]
    113204            [11056|15077]
    114205
    115         Note that this is just like block matrix multiplication:
     206        Note that this is just like block matrix multiplication::
     207
    116208            sage: M.subdivision(0,0) * N.subdivision(0,0) + M.subdivision(0,1) * N.subdivision(1,0)
    117209            [1048]
    118210            [3056]
    119211           
    120212        If the subdivisions aren't compatible, ignore them.
     213        ::
     214
    121215            sage: N.subdivide(1,1); N
    122216            [ 0| 1]
    123217            [--+--]
     
    156250cdef class MatrixVectorAction(MatrixMulAction):
    157251    def __init__(self, G, S):
    158252        """
    159         EXAMPLES:
     253        EXAMPLES::
     254
    160255            sage: from sage.matrix.action import MatrixVectorAction
    161256            sage: A = MatrixVectorAction(MatrixSpace(QQ, 3, 3), VectorSpace(CDF, 4)); A
    162257            Traceback (most recent call last):
     
    169264       
    170265    def _create_codomain(self, base):
    171266        """
    172         EXAMPLES:
     267        EXAMPLES::
     268
    173269            sage: from sage.matrix.action import MatrixVectorAction
    174270            sage: A = MatrixVectorAction(MatrixSpace(QQ, 5, 3), VectorSpace(CDF, 3)); A
    175271            Left action by Full MatrixSpace of 5 by 3 dense matrices over Rational Field on Vector space of dimension 3 over Complex Double Field
    176272            sage: A.codomain()
    177273            Vector space of dimension 5 over Complex Double Field
    178274        """
    179         if self.G.ncols() != self.S.degree():
    180             raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),  self.S.degree())
     275        if self.G.ncols() != self.underlying_set().degree():
     276            raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),
     277                                                                 self.underlying_set().degree())
    181278        return FreeModule(base, self.G.nrows(), sparse = self.G.is_sparse())
    182279       
    183280    cpdef _call_(self, g, s):
     
    198295cdef class VectorMatrixAction(MatrixMulAction):
    199296    def __init__(self, G, S):
    200297        """
    201         EXAMPLES:
     298        EXAMPLES::
     299
    202300            sage: from sage.matrix.action import VectorMatrixAction
    203301            sage: A = VectorMatrixAction(MatrixSpace(QQ, 5, 3), VectorSpace(CDF, 3)); A
    204302            Traceback (most recent call last):
     
    211309       
    212310    def _create_codomain(self, base):
    213311        """
    214         EXAMPLES:
     312        EXAMPLES::
     313
    215314            sage: from sage.matrix.action import VectorMatrixAction
    216315            sage: A = VectorMatrixAction(MatrixSpace(QQ, 3, 5), VectorSpace(CDF, 3)); A
    217316            Right action by Full MatrixSpace of 3 by 5 dense matrices over Rational Field on Vector space of dimension 3 over Complex Double Field
    218317            sage: A.codomain()
    219318            Vector space of dimension 5 over Complex Double Field
    220319        """
    221         if self.G.nrows() != self.S.degree():
    222             raise TypeError, "incompatible dimensions %s, %s" % (self.G.nrows(), self.S.degree())
     320        if self.G.nrows() != self.underlying_set().degree():
     321            raise TypeError, "incompatible dimensions %s, %s" % (self.G.nrows(),
     322                                                                 self.underlying_set().degree())
    223323        return FreeModule(base, self.G.ncols(), sparse = self.G.is_sparse())
    224324       
    225325    cpdef _call_(self, s, g):
  • sage/rings/polynomial/multi_polynomial_libsingular.pyx

    diff --git a/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/sage/rings/polynomial/multi_polynomial_libsingular.pyx
    a b  
    243243import sage.libs.pari.gen
    244244import polynomial_element
    245245
     246permstore=[]
    246247cdef class MPolynomialRing_libsingular(MPolynomialRing_generic):
    247248
    248249    def __cinit__(self):
     
    364365        from sage.rings.polynomial.polynomial_element import PolynomialBaseringInjection
    365366        base_inject = PolynomialBaseringInjection(base_ring, self)
    366367        self.register_coercion(base_inject)
     368        #permanently store a reference to this ring until deallocation works reliably
     369        permstore.append(self)
    367370
    368371    def __dealloc__(self):
    369372        r"""
  • sage/rings/polynomial/polynomial_ring_constructor.py

    diff --git a/sage/rings/polynomial/polynomial_ring_constructor.py b/sage/rings/polynomial/polynomial_ring_constructor.py
    a b  
    5353from sage.categories.commutative_rings import CommutativeRings
    5454_CommutativeRings = CommutativeRings()
    5555
    56 _cache = {}
     56import weakref
     57_cache = weakref.WeakValueDictionary()
    5758
    5859def PolynomialRing(base_ring, arg1=None, arg2=None,
    5960                   sparse=False, order='degrevlex',
  • sage/structure/coerce.pxd

    diff --git a/sage/structure/coerce.pxd b/sage/structure/coerce.pxd
    a b  
    44from parent cimport Parent
    55from sage.categories.action cimport Action
    66
    7 from coerce_dict cimport TripleDict, TripleDictIter
     7from coerce_dict cimport TripleDict
    88
    99cdef class CoercionModel_cache_maps(CoercionModel):
    1010    # This MUST be a mapping to tuples, where each
  • sage/structure/coerce.pyx

    diff --git a/sage/structure/coerce.pyx b/sage/structure/coerce.pyx
    a b  
    12071207        try:
    12081208            return self._action_maps.get(R, S, op)
    12091209        except KeyError:
    1210             action = self.discover_action(R, S, op)
    1211             action = self.verify_action(action, R, S, op)
    1212             self._action_maps.set(R, S, op, action)
    1213             return action
     1210            pass
     1211        action = self.discover_action(R, S, op)
     1212        action = self.verify_action(action, R, S, op)
     1213        self._action_maps.set(R, S, op, action)
     1214        return action
    12141215           
    12151216    cpdef verify_action(self, action, R, S, op, bint fix=True):
    12161217        r"""
  • sage/structure/coerce_actions.pyx

    diff --git a/sage/structure/coerce_actions.pyx b/sage/structure/coerce_actions.pyx
    a b  
    100100            Multivariate Polynomial Ring in x, y, z over Rational Field
    101101        """
    102102        if self._codomain is None:
    103             self._codomain = parent_c(self.act(an_element(self.G), an_element(self.S)))
     103            self._codomain = parent_c(self.act(an_element(self.G),
     104                                               an_element(self.underlying_set())))
    104105        return self._codomain
    105106
    106107
     
    332333        """
    333334        if self.extended_base is not None:
    334335            return self.extended_base
    335         return self.S
     336        return self.underlying_set()
    336337       
    337338    def domain(self):
    338339        """
     
    345346            sage: A.domain()
    346347            Multivariate Polynomial Ring in x, y, z over Integer Ring
    347348        """
    348         return self.S
     349        return self.underlying_set()
    349350
    350351
    351352
  • sage/structure/coerce_dict.pxd

    diff --git a/sage/structure/coerce_dict.pxd b/sage/structure/coerce_dict.pxd
    a b  
    11cdef class TripleDict:
    22    cdef Py_ssize_t _size
    33    cdef buckets
     4    cdef dict _refcache
    45    cdef double threshold
    5     cdef get(self, k1, k2, k3)
    6     cdef set(self, k1, k2, k3, value)
     6    cdef TripleDictEraser eraser
     7    cdef get(self, object k1, object k2, object k3)
     8    cdef set(self, object k1, object k2, object k3, value)
    79   
    8 cdef class TripleDictIter:
    9     cdef TripleDict pairs
    10     cdef buckets, bucket_iter
     10cdef class TripleDictEraser:
     11    cdef TripleDict D
  • sage/structure/coerce_dict.pyx

    diff --git a/sage/structure/coerce_dict.pyx b/sage/structure/coerce_dict.pyx
    a b  
    11#*****************************************************************************
    22#       Copyright (C) 2007 Robert Bradshaw <robertwb@math.washington.edu>
     3#                     2012 Simon King <simon.king@uni-jena.de>
    34#
    45#  Distributed under the terms of the GNU General Public License (GPL)
    56#
    67#                  http://www.gnu.org/licenses/
    78#*****************************************************************************
     9"""
     10Containers for storing coercion data
    811
     12This module provides :class:`TripleDict`. It is a structure similar to
     13``WeakKeyDictionary`` in Python's weakref module, and is optimized for lookup
     14speed. Keys consist of a triple (k1,k2,k3) and are looked up by identity
     15rather than equality. The keys are stored by weakrefs if possible. If any
     16one of the components k1, k2, k3 gets garbage collected, then the entry is
     17removed from the :class:`TripleDict`.
    918
     19Key components that do not allow for weakrefs are stored via a normal
     20refcounted reference. That means that any entry stored using a triple
     21(k1,k2,k3) so that none of the k1,k2,k3 allows a weak reference behaves
     22as an entry in a normal dictionary: Its existence in :class:`TripleDict`
     23prevents it from being garbage collected.
     24
     25That container currently is used to store coercion and conversion maps
     26between two parents (:trac:`715`) and to store homsets of pairs of objects
     27of a category (:trac:`11521`). In both cases, it is essential that the parent
     28structures remain garbage collectable, it is essential that the data access
     29is faster than with a usual ``WeakKeyDictionary``, and we enforce the "unique
     30parent condition" in Sage (parent structures should be identical if they are
     31equal).
     32"""
    1033include "../ext/python_list.pxi"
    1134
     35from weakref import KeyedRef
     36
     37############################################
     38# The following code is responsible for
     39# removing dead references from the cache
     40############################################
     41
     42cdef class TripleDictEraser:
     43    """
     44    Erases items from a :class:`TripleDict` when a weak reference becomes
     45    invalid.
     46
     47    This is of internal use only. Instances of this class will be passed as a
     48    callback function when creating a weak reference.
     49
     50    EXAMPLES::
     51
     52        sage: from sage.structure.coerce_dict import TripleDict
     53        sage: class A: pass
     54        sage: a = A()
     55        sage: T = TripleDict(11)
     56        sage: T[a,ZZ,None] = 1
     57        sage: T[ZZ,a,1] = 2
     58        sage: T[a,a,ZZ] = 3
     59        sage: len(T)
     60        3
     61        sage: del a
     62        sage: import gc
     63        sage: n = gc.collect()
     64        sage: len(T)
     65        0
     66
     67    AUTHOR:
     68
     69    - Simon King (2012-01)
     70    """
     71
     72    def __init__(self, D):
     73        """
     74        INPUT:
     75
     76        A :class:`TripleDict`.
     77
     78        EXAMPLES::
     79
     80            sage: from sage.structure.coerce_dict import TripleDict, TripleDictEraser
     81            sage: D = TripleDict(11)
     82            sage: TripleDictEraser(D)
     83            <sage.structure.coerce_dict.TripleDictEraser object at ...>
     84
     85        """
     86        self.D = D
     87
     88    def __call__(self, r):
     89        """
     90        INPUT:
     91
     92        A weak reference with key.
     93
     94        When this is called with a weak reference ``r``, then each item
     95        containing ``r`` is removed from the associated :class:`TripleDict`.
     96        Normally, this only happens when a weak reference becomes invalid.
     97
     98        EXAMPLES::
     99
     100            sage: from sage.structure.coerce_dict import TripleDict
     101            sage: class A: pass
     102            sage: a = A()
     103            sage: T = TripleDict(11)
     104            sage: T[a,ZZ,None] = 1
     105            sage: T[ZZ,a,1] = 2
     106            sage: T[a,a,ZZ] = 3
     107            sage: len(T)
     108            3
     109            sage: del a
     110            sage: import gc
     111            sage: n = gc.collect()
     112            sage: len(T)    # indirect doctest
     113            0
     114        """
     115        # r is a (weak) reference (typically to a parent), and it knows the
     116        # stored key of the unique triple r() had been part of.
     117        # We remove that unique triple from self.D
     118        cdef size_t k1,k2,k3
     119        k1,k2,k3 = r.key
     120        cdef size_t h = (k1 + 13*k2 ^ 503*k3)
     121        cdef list bucket = <object>PyList_GET_ITEM(self.D.buckets, h % PyList_GET_SIZE(self.D.buckets))
     122        cdef int i
     123        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
     124            if <size_t><object>PyList_GET_ITEM(bucket, i)==k1 and \
     125               <size_t><object>PyList_GET_ITEM(bucket, i+1)==k2 and \
     126               <size_t><object>PyList_GET_ITEM(bucket, i+2)==k3:
     127                del bucket[i:i+4]
     128                self.D._size -= 1
     129                break
     130        try:
     131            self.D._refcache.__delitem__((k1,k2,k3))
     132        except KeyError:
     133            pass
    12134
    13135cdef class TripleDict:
    14136    """
    15     This is a hashtable specifically designed for (read) speed in 
    16     the coercion model. 
    17    
    18     It differs from a python dict in the following important ways: 
    19    
     137    This is a hashtable specifically designed for (read) speed in
     138    the coercion model.
     139
     140    It differs from a python dict in the following important ways:
     141
    20142       - All keys must be sequence of exactly three elements. All sequence
    21          types (tuple, list, etc.) map to the same item. 
     143         types (tuple, list, etc.) map to the same item.
    22144       - Comparison is done using the 'is' rather than '==' operator.
    23        
    24     There are special cdef set/get methods for faster access. 
    25     It is bare-bones in the sense that not all dictionary methods are 
    26     implemented. 
    27    
    28     It is implemented as a list of lists (hereafter called buckets). The bucket 
    29     is chosen according to a very simple hash based on the object pointer.
    30     and each bucket is of the form [k1, k2, k3, value, k1, k2, k3, value, ...]
    31     on which a linear search is performed.
    32    
     145
     146    There are special cdef set/get methods for faster access.
     147    It is bare-bones in the sense that not all dictionary methods are
     148    implemented.
     149
     150    It is implemented as a list of lists (hereafter called buckets). The bucket
     151    is chosen according to a very simple hash based on the object pointer,
     152    and each bucket is of the form [id(k1), id(k2), id(k3), value, id(k1),
     153    id(k2), id(k3), value, ...], on which a linear search is performed.
     154
    33155    To spread objects evenly, the size should ideally be a prime, and certainly
    34     not divisible by 2.
    35    
    36    
    37     EXAMPLES:
    38    
     156    not divisible by 2.
     157
     158    EXAMPLES::
     159
    39160        sage: from sage.structure.coerce_dict import TripleDict
    40161        sage: L = TripleDict(31)
    41162        sage: a = 'a'; b = 'b'; c = 'c'
     
    82203        Traceback (most recent call last):
    83204        ...
    84205        KeyError: 'a'
    85        
    86     The following illustrates why even sizes are bad.
    87         sage: L = TripleDict(4, L)
     206
     207    The following illustrates why even sizes are bad (setting the threshold
     208    zero, so that no beneficial resizing happens)::
     209
     210        sage: L = TripleDict(4, L, threshold=0)
    88211        sage: L.stats()
    89212        (0, 250.25, 1001)
    90213        sage: L.bucket_lens()
    91214        [1001, 0, 0, 0]
    92215
     216    Note that this kind of dictionary is also used for caching actions
     217    and coerce maps. In previous versions of Sage, the cache was by
     218    strong references and resulted in a memory leak in the following
     219    example. However, this leak was fixed by trac ticket :trac:`715`,
     220    using weak references::
    93221
    94     AUTHOR:
    95        -- Robert Bradshaw, 2007-08
     222        sage: K = GF(1<<55,'t')
     223        sage: for i in range(50):
     224        ...     a = K.random_element()
     225        ...     E = EllipticCurve(j=a)
     226        ...     P = E.random_point()
     227        ...     Q = 2*P
     228        sage: import gc
     229        sage: n = gc.collect()
     230        sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field
     231        sage: LE = [x for x in gc.get_objects() if isinstance(x, EllipticCurve_finite_field)]
     232        sage: len(LE)    # indirect doctest
     233        1
     234
     235    ..NOTE::
     236
     237        The index `h` corresponding to the key [k1, k2, k3] is computed as a
     238        value of unsigned type size_t as follows:
     239
     240        ..MATH::
     241
     242            h = id(k1) + 13*id(k2) \oplus 503 id(k3)
     243
     244        Indeed, although the PyList_GetItem function and corresponding
     245        PyList_GET_ITEM macro take a value of signed type Py_ssize_t as input
     246        for the index, they do not accept negative inputs as the higher level
     247        Python functions. Moreover, the above formula can overflow so that `h`
     248        might be considered as negative. Even though this value is taken
     249        modulo the size of the buckets' list before accessing the corresponding
     250        item, the Cython "%" operator behaves for values of type size_t and
     251        Py_ssize_t like the C "%" operator, rather than like the Python "%"
     252        operator as it does for values of type int. That is, it returns a
     253        result of the same sign as its input. Therefore, if `h` was defined as
     254        a signed value, we might access the list at a negative index and raise
     255        a segfault (and this has been observed on 32 bits systems, see
     256        :trac:`715` for details).
     257
     258    AUTHORS:
     259
     260    - Robert Bradshaw, 2007-08
     261
     262    - Simon King, 2012-01
    96263    """
    97    
    98     def __init__(self, size, data=None, threshold=0):
     264
     265    def __init__(self, size, data=None, threshold=0.7):
    99266        """
    100         Create a special dict using triples for keys.
    101        
    102         EXAMPLES:
     267        Create a special dict using triples for keys.
     268
     269        EXAMPLES::
     270
    103271            sage: from sage.structure.coerce_dict import TripleDict
    104272            sage: L = TripleDict(31)
    105273            sage: a = 'a'; b = 'b'; c = 'c'
     
    111279        self.threshold = threshold
    112280        self.buckets = [[] for i from 0 <= i <  size]
    113281        self._size = 0
     282        self.eraser = TripleDictEraser(self)
     283        self._refcache = {}
    114284        if data is not None:
    115             for k, v in data.iteritems():
    116                 self[k] = v
    117                
     285            for (k1,k2,k3), v in data.iteritems():
     286                self.set(k1,k2,k3, v)
     287
    118288    def __len__(self):
    119289        """
    120290        The number of items in self.
    121        
    122         EXAMPLES:
     291
     292        EXAMPLES::
     293
    123294            sage: from sage.structure.coerce_dict import TripleDict
    124295            sage: L = TripleDict(37)
    125296            sage: a = 'a'; b = 'b'; c = 'c'
     
    131302            3
    132303        """
    133304        return self._size
    134        
     305
    135306    def stats(self):
    136307        """
    137         The distribution of items in buckets.
    138        
    139         OUTPUT:
    140             (min, avg, max)
    141        
    142         EXAMPLES:
     308        The distribution of items in buckets.
     309
     310        OUTPUT:
     311
     312        - (min, avg, max)
     313
     314        EXAMPLES::
     315
    143316            sage: from sage.structure.coerce_dict import TripleDict
    144317            sage: L = TripleDict(37)
    145318            sage: for i in range(100): L[i,i,i] = None
     
    150323            sage: for i in range(100): L[i,i,i] = None
    151324            sage: L.stats() # random
    152325            (0, 0.03325573661456601, 1)
    153            
    154             sage: L = TripleDict(1)
     326
     327        In order to have a test that isn't random, we use parameters
     328        that should not be used in real applications::
     329
     330            sage: L = TripleDict(1, threshold=0)
    155331            sage: for i in range(100): L[i,i,i] = None
    156332            sage: L.stats()
    157333            (100, 100.0, 100)
     
    166342            else:
    167343                min = 0
    168344        return min, 1.0*size/len(self.buckets), max
    169        
     345
    170346    def bucket_lens(self):
    171347        """
    172         The distribution of items in buckets.
    173        
    174         OUTPUT:
    175             A list of how many items are in each bucket.
    176        
    177         EXAMPLES:
     348        The distribution of items in buckets.
     349
     350        OUTPUT:
     351
     352        A list of how many items are in each bucket.
     353
     354        EXAMPLES::
     355
    178356            sage: from sage.structure.coerce_dict import TripleDict
    179             sage: L = TripleDict(37)
     357            sage: L = TripleDict(37, threshold=0)
    180358            sage: for i in range(100): L[i,i,i] = None
    181359            sage: L.bucket_lens() # random
    182360            [3, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4]
    183361            sage: sum(L.bucket_lens())
    184362            100
    185            
    186             sage: L = TripleDict(1)
     363
     364        In order to have a test that isn't random, we use parameters
     365        that should not be used in real applications::
     366
     367            sage: L = TripleDict(1, threshold=0)
    187368            sage: for i in range(100): L[i,i,i] = None
    188369            sage: L.bucket_lens()
    189370            [100]
    190371        """
    191372        return [len(self.buckets[i])/4 for i from 0 <= i < len(self.buckets)]
    192        
     373
    193374    def _get_buckets(self):
    194375        """
    195         The actual buckets of self, for debugging.
    196        
    197         EXAMPLE:
     376        The actual buckets of self, for debugging.
     377
     378        EXAMPLES::
     379
    198380            sage: from sage.structure.coerce_dict import TripleDict
    199381            sage: L = TripleDict(3)
    200382            sage: L[0,0,0] = None
     
    202384            [[0, 0, 0, None], [], []]
    203385        """
    204386        return self.buckets
    205        
     387
    206388    def __getitem__(self, k):
    207389        """
    208         EXAMPLES:
     390        EXAMPLES::
     391
    209392            sage: from sage.structure.coerce_dict import TripleDict
    210393            sage: L = TripleDict(31)
    211394            sage: a = 'a'; b = 'b'; c = 'c'
     
    213396            sage: L[a,b,c]
    214397            1
    215398        """
     399        cdef object k1,k2,k3
    216400        try:
    217401            k1, k2, k3 = k
    218402        except (TypeError,ValueError):
    219403            raise KeyError, k
    220404        return self.get(k1, k2, k3)
    221            
    222     cdef get(self, k1, k2, k3):
    223         cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
    224         if h < 0: h = -h
     405
     406    cdef get(self, object k1, object k2, object k3):
     407        cdef size_t h1,h2,h3
     408        h1 = <size_t><void *>k1
     409        h2 = <size_t><void *>k2
     410        h3 = <size_t><void *>k3
     411        cdef object r1,r2,r3
     412        try:
     413            r1,r2,r3 = self._refcache[h1,h2,h3]
     414        except KeyError:
     415            raise KeyError, (k1,k2,k3)
     416        if (isinstance(r1,KeyedRef) and r1() is None) or \
     417           (isinstance(r2,KeyedRef) and r2() is None) or \
     418           (isinstance(r3,KeyedRef) and r3() is None):
     419            raise KeyError, (k1,k2,k3)
     420        cdef size_t h = (h1 + 13*h2 ^ 503*h3)
    225421        cdef Py_ssize_t i
    226         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     422        cdef list all_buckets = self.buckets
     423        cdef list bucket = <object>PyList_GET_ITEM(all_buckets, h % PyList_GET_SIZE(all_buckets))
     424        cdef object tmp
    227425        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
    228             if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
    229                PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
    230                PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
    231                 return <object>PyList_GET_ITEM(bucket, i+3)
     426            tmp = <object>PyList_GET_ITEM(bucket, i)
     427            if <size_t>tmp == <size_t><void *>k1:
     428                tmp = <object>PyList_GET_ITEM(bucket, i+1)
     429                if <size_t>tmp == <size_t><void *>k2:
     430                    tmp = <object>PyList_GET_ITEM(bucket, i+2)
     431                    if <size_t>tmp == <size_t><void *>k3:
     432                        return <object>PyList_GET_ITEM(bucket, i+3)
    232433        raise KeyError, (k1, k2, k3)
    233        
     434
    234435    def __setitem__(self, k, value):
    235436        """
    236         EXAMPLES:
     437        EXAMPLES::
     438
    237439            sage: from sage.structure.coerce_dict import TripleDict
    238440            sage: L = TripleDict(31)
    239441            sage: a = 'a'; b = 'b'; c = 'c'
     
    241443            sage: L[a,b,c]
    242444            -1
    243445        """
     446        cdef object k1,k2,k3
    244447        try:
    245448            k1, k2, k3 = k
    246449        except (TypeError,ValueError):
    247450            raise KeyError, k
    248451        self.set(k1, k2, k3, value)
    249            
    250     cdef set(self, k1, k2, k3, value):
     452
     453    cdef set(self, object k1, object k2, object k3, value):
    251454        if self.threshold and self._size > len(self.buckets) * self.threshold:
    252455            self.resize()
    253         cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
    254         if h < 0: h = -h
     456        cdef size_t h1 = <size_t><void *>k1
     457        cdef size_t h2 = <size_t><void *>k2
     458        cdef size_t h3 = <size_t><void *>k3
     459        cdef size_t h = (h1 + 13*h2 ^ 503*h3)
    255460        cdef Py_ssize_t i
    256         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     461        cdef list bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     462        cdef object tmp
    257463        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
    258             if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
    259                PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
    260                PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
    261                 bucket[i+3] = value
    262                 return
    263         bucket += [k1, k2, k3, value]
     464            tmp = <object>PyList_GET_ITEM(bucket, i)
     465            if <size_t>tmp == h1:
     466                tmp = <object>PyList_GET_ITEM(bucket, i+1)
     467                if <size_t>tmp == h2:
     468                    tmp = <object>PyList_GET_ITEM(bucket, i+2)
     469                    if <size_t>tmp == h3:
     470                        # Test whether the old references are still active
     471                        r1,r2,r3 = <tuple>(self._refcache[h1,h2,h3])
     472                        if (isinstance(r1,KeyedRef) and r1() is None) or \
     473                           (isinstance(r2,KeyedRef) and r2() is None) or \
     474                           (isinstance(r3,KeyedRef) and r3() is None):
     475                            del bucket [i:i+4]
     476                            self._size -= 1
     477                            break
     478                        bucket[i+3] = value
     479                        return
     480        PyList_Append(bucket, h1)
     481        PyList_Append(bucket, h2)
     482        PyList_Append(bucket, h3)
     483        PyList_Append(bucket, value)
     484        try:
     485            ref1 = KeyedRef(k1,self.eraser,(h1, h2, h3))
     486        except TypeError:
     487            ref1 = k1
     488        if k2 is not k1:
     489            try:
     490                ref2 = KeyedRef(k2,self.eraser,(h1, h2, h3))
     491            except TypeError:
     492                ref2 = k2
     493        else:
     494            ref2 = None
     495        if k3 is not k2 or k3 is not k1:
     496            try:
     497                ref3 = KeyedRef(k3,self.eraser,(h1, h2, h3))
     498            except TypeError:
     499                ref3 = k3
     500        else:
     501            ref3 = None
     502        self._refcache[h1,h2,h3] = (ref1,ref2,ref3)
    264503        self._size += 1
    265            
     504
    266505    def __delitem__(self, k):
    267506        """
    268         EXAMPLES:
     507        EXAMPLES::
     508
    269509            sage: from sage.structure.coerce_dict import TripleDict
    270510            sage: L = TripleDict(31)
    271511            sage: a = 'a'; b = 'b'; c = 'c'
     
    274514            sage: len(L)
    275515            0
    276516        """
     517        cdef object k1,k2,k3
    277518        try:
    278519            k1, k2, k3 = k
    279520        except (TypeError,ValueError):
    280521            raise KeyError, k
    281         cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
    282         if h < 0: h = -h
     522        try:
     523            r1,r2,r3 = self._refcache[<size_t><void *>k1,<size_t><void *>k2,<size_t><void *>k3]
     524        except KeyError:
     525            raise KeyError, k
     526        if (isinstance(r1,KeyedRef) and r1() is None) or \
     527           (isinstance(r2,KeyedRef) and r2() is None) or \
     528           (isinstance(r3,KeyedRef) and r3() is None):
     529            raise KeyError, k
     530        try:
     531            del self._refcache[<size_t><void *>k1,<size_t><void *>k2,<size_t><void *>k3]
     532        except KeyError:
     533            # This is to cope with a potential racing condition - if garbage
     534            # collection and weakref callback happens right between the
     535            # "if (isinstance(r1,..." and the "del", then the previously
     536            # existing entry might already be gone.
     537            raise KeyError, k
     538        cdef size_t h = (<size_t><void *>k1 + 13*<size_t><void *>k2 ^ 503*<size_t><void *>k3)
    283539        cdef Py_ssize_t i
    284         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     540        cdef list bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
    285541        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
    286             if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
    287                PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
    288                PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
     542            if <size_t><object>PyList_GET_ITEM(bucket, i) == <size_t><void *>k1 and \
     543               <size_t><object>PyList_GET_ITEM(bucket, i+1) == <size_t><void *>k2 and \
     544               <size_t><object>PyList_GET_ITEM(bucket, i+2) == <size_t><void *>k3:
    289545                del bucket[i:i+4]
    290546                self._size -= 1
    291547                return
    292548        raise KeyError, k
    293        
     549
    294550    def resize(self, int buckets=0):
    295551        """
    296         Changes the number of buckets of self, while preserving the contents.
    297        
    298         If the number of buckets is 0 or not given, it resizes self to the
    299         smallest prime that is at least twice as large as self.
    300        
    301         EXAMPLES:
     552        Changes the number of buckets of self, while preserving the contents.
     553
     554        If the number of buckets is 0 or not given, it resizes self to the
     555        smallest prime that is at least twice as large as self.
     556
     557        EXAMPLES::
     558
    302559            sage: from sage.structure.coerce_dict import TripleDict
    303560            sage: L = TripleDict(8)
    304561            sage: for i in range(100): L[i,i,i] = None
     
    312569        """
    313570        if buckets == 0:
    314571            buckets = next_odd_prime(2*len(self.buckets))
    315         cdef TripleDict new = TripleDict(buckets, self)
    316         self.buckets = new.buckets
    317            
     572        cdef list old_buckets = self.buckets
     573        cdef list bucket
     574        cdef Py_ssize_t i
     575        cdef size_t h
     576        self.buckets = [[] for i from 0 <= i <  buckets]
     577        cdef size_t k1,k2,k3
     578        cdef object v
     579        for bucket in old_buckets:
     580            for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
     581                k1 = <size_t><object>PyList_GET_ITEM(bucket, i)
     582                k2 = <size_t><object>PyList_GET_ITEM(bucket, i+1)
     583                k3 = <size_t><object>PyList_GET_ITEM(bucket, i+2)
     584                v  = <object>PyList_GET_ITEM(bucket, i+3)
     585                h = (k1 + 13*k2 ^ 503*k3)
     586                self.buckets[h % buckets] += [k1,k2,k3,v]
     587
    318588    def iteritems(self):
    319589        """
    320         EXAMPLES:
     590        EXAMPLES::
     591
    321592            sage: from sage.structure.coerce_dict import TripleDict
    322593            sage: L = TripleDict(31)
    323594            sage: L[1,2,3] = None
    324595            sage: list(L.iteritems())
    325596            [((1, 2, 3), None)]
    326597        """
    327         return TripleDictIter(self)
    328        
     598        cdef list bucket
     599        cdef size_t i, h1,h2,h3
     600        # We test whether the references are still valid.
     601        # However, we must not delete them, since we are
     602        # iterating.
     603        for bucket in self.buckets:
     604            for i from 0<=i<len(bucket) by 4:
     605                h1,h2,h3 = bucket[i:i+3]
     606                try:
     607                    r1,r2,r3 = self._refcache[h1,h2,h3]
     608                except KeyError:
     609                    # That can only happen under a race condition.
     610                    # Anyway, it means the item is not there.
     611                    continue
     612                if isinstance(r1, KeyedRef):
     613                    r1 = r1()
     614                    if r1 is None:
     615                        continue
     616                if isinstance(r2, KeyedRef):
     617                    r2 = r2()
     618                    if r2 is None:
     619                        continue
     620                if isinstance(r3, KeyedRef):
     621                    r3 = r3()
     622                    if r3 is None:
     623                        continue
     624                yield (r1,r2,r3), <object>PyList_GET_ITEM(bucket,i+3)
     625
    329626    def __reduce__(self):
    330627        """
    331         Note that we don't expect equality as this class concerns itself with 
    332         object identity rather than object equality. 
     628        Note that we don't expect equality as this class concerns itself with
     629        object identity rather than object equality.
    333630
    334         EXAMPLES:
     631        EXAMPLES::
     632
    335633            sage: from sage.structure.coerce_dict import TripleDict
    336634            sage: L = TripleDict(31)
    337635            sage: L[1,2,3] = True
     
    341639            [((1, 2, 3), True)]
    342640        """
    343641        return TripleDict, (len(self.buckets), dict(self.iteritems()), self.threshold)
    344        
    345 
    346 cdef class TripleDictIter:
    347     def __init__(self, pairs):
    348         """
    349         EXAMPLES:
    350             sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    351             sage: L = TripleDict(31)
    352             sage: L[1,2,3] = None
    353             sage: L.iteritems().next()
    354             ((1, 2, 3), None)
    355         """
    356         self.pairs = pairs
    357         self.buckets = iter(self.pairs.buckets)
    358     def __iter__(self):
    359         """
    360         EXAMPLES:
    361             sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    362             sage: L = TripleDict(31)
    363             sage: L[1,2,3] = None
    364             sage: iter(L.iteritems()).next()
    365             ((1, 2, 3), None)
    366         """
    367         return self
    368     def __next__(self):
    369         """
    370         EXAMPLES:
    371             sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    372             sage: L = TripleDict(31)
    373             sage: L[1,2,3] = None
    374             sage: L[3,2,1] = None
    375             sage: sorted(L.iteritems())
    376             [((1, 2, 3), None), ((3, 2, 1), None)]
    377         """
    378         while self.bucket_iter is None:
    379             self.bucket_iter = self.buckets.next()
    380         self.bucket_iter = iter(self.bucket_iter)
    381         try:
    382             k1 = self.bucket_iter.next()
    383             k2 = self.bucket_iter.next()
    384             k3 = self.bucket_iter.next()
    385             value = self.bucket_iter.next()
    386             return ((k1, k2, k3), value)
    387         except StopIteration:
    388             self.bucket_iter = None
    389             return self.next()
    390 
    391 
    392642
    393643cdef long next_odd_prime(long n):
    394644    if n % 2 == 0:
  • sage/structure/parent.pxd

    diff --git a/sage/structure/parent.pxd b/sage/structure/parent.pxd
    a b  
    77###############################################################################
    88
    99cimport sage.structure.category_object
     10from sage.structure.coerce_dict cimport TripleDict
    1011
    1112cdef class Parent(category_object.CategoryObject):
    1213
     
    7374    # and Parents for which self._rmul_ and/or self._lmul_
    7475    # do the correct thing.
    7576    # Initialized at ring creation.
    76     cdef _action_list
     77    cdef list _action_list
    7778    # Hashtable of everything we've (possibly recursively) discovered so far.
    78     cdef _action_hash
     79    cdef TripleDict _action_hash
    7980
    8081    # List consisting of Morphisms (from anything to self)
    8182    # and Parents for which the __call__ method of self
  • sage/structure/parent.pyx

    diff --git a/sage/structure/parent.pyx b/sage/structure/parent.pyx
    a b  
    546546            self._coerce_from_list = []
    547547            self._coerce_from_hash = {}
    548548            self._action_list = []
    549             self._action_hash = {}
     549            self._action_hash = TripleDict(23)
    550550            self._convert_from_list = []
    551551            self._convert_from_hash = {}
    552552            self._embedding = None
     
    664664        EXAMPLES::
    665665       
    666666            sage: sorted(QQ._introspect_coerce().items())
    667             [('_action_hash', {...}),
     667            [('_action_hash', <sage.structure.coerce_dict.TripleDict object at ...>),
    668668             ('_action_list', []),
    669669             ('_coerce_from_hash', {...}),
    670670             ('_coerce_from_list', []),
     
    16061606        if isinstance(action, Action):
    16071607            if action.actor() is self:
    16081608                self._action_list.append(action)
    1609                 self._action_hash[action.domain(), action.operation(), action.is_left()] = action
     1609                self._action_hash.set(action.domain(), action.operation(), action.is_left(), action)
    16101610            elif action.domain() is self:
    16111611                self._action_list.append(action)
    1612                 self._action_hash[action.actor(), action.operation(), not action.is_left()] = action
     1612                self._action_hash.set(action.actor(), action.operation(), not action.is_left(), action)
    16131613            else:
    16141614                raise ValueError("Action must involve self")
    16151615        else:
     
    22192219        try:
    22202220            if self._action_hash is None: # this is because parent.__init__() does not always get called
    22212221                self.init_coerce()
    2222             return self._action_hash[S, op, self_on_left]
     2222            return self._action_hash.get(S, op, self_on_left)
    22232223        except KeyError:
    22242224            pass
    22252225
     
    22342234            # We do NOT add to the list, as this would lead to errors as in
    22352235            # the example above.
    22362236
    2237         self._action_hash[S, op, self_on_left] = action
     2237        self._action_hash.set(S, op, self_on_left, action)
    22382238        return action
    22392239       
    22402240
  • sage/structure/parent_old.pyx

    diff --git a/sage/structure/parent_old.pyx b/sage/structure/parent_old.pyx
    a b  
    3030import operator
    3131from parent import Set_PythonType, Set_PythonType_class
    3232from coerce import py_scalar_parent
     33from sage.structure.coerce_dict import TripleDict
    3334
    3435include '../ext/python_object.pxi'
    3536include '../ext/python_bool.pxi'
     
    6667        self._coerce_from_list = list(coerce_from)
    6768        self._coerce_from_hash = {}
    6869        self._action_list = list(actions)
    69         self._action_hash = {}
     70        self._action_hash = TripleDict(23)
    7071       
    7172        cdef parent.Parent other
    7273        for mor in embeddings: