Ticket #715: trac715_tripledict_combined.patch

File trac715_tripledict_combined.patch, 51.7 KB (added by SimonKing, 8 years ago)

Introduce weak references to coercion dicts, and refactor the hashtables.

  • sage/categories/action.pxd

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1324507434 -3600
    # Node ID f6b0e8e8b8802e00132b903f27548aed28d4ba91
    # Parent  6120ec4f0db167c65c125f80b2d405ccb49d1da1
    #715: Use weak references for caching coerce maps, actions and polynomial rings.
    Introduce a second variant of `TripleDict`.
    Use a weak reference to the underlying set of an action. Use TripleDictById to store actions in parents.
    
    diff --git a/sage/categories/action.pxd b/sage/categories/action.pxd
    a b  
    88    cdef S
    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  
    3232
    3333import homset
    3434import sage.structure.element
     35from weakref import ref
    3536
    3637include "../ext/stdsage.pxi"
    3738
     
    4849        from groupoid import Groupoid
    4950        Functor.__init__(self, Groupoid(G), category(S))
    5051        self.G = G
    51         self.S = S
     52        self.S = ref(S)
    5253        self._is_left = is_left
    5354        self.op = op
    5455       
     
    6162            if g in self.G:
    6263                return ActionEndomorphism(self, self.G(g))
    6364            elif g == self.G:
    64                 return self.S
     65                return self.underlying_set()
    6566            else:
    6667                raise TypeError, "%s not an element of %s"%(g, self.G)
    6768        elif len(args) == 2:
    6869            if self._is_left:
    69                 return self._call_(self.G(args[0]), self.S(args[1]))
     70                return self._call_(self.G(args[0]), self.underlying_set()(args[1]))
    7071            else:
    71                 return self._call_(self.S(args[0]), self.G(args[1]))
     72                return self._call_(self.underlying_set()(args[0]), self.G(args[1]))
    7273           
    7374    cpdef _call_(self, a, b):
    7475        raise NotImplementedError, "Action not implemented."
     
    9192       
    9293    def __repr__(self):
    9394        side = "Left" if self._is_left else "Right"
    94         return "%s %s by %r on %r"%(side, self._repr_name_(), self.G, self.S)
     95        return "%s %s by %r on %r"%(side, self._repr_name_(), self.G,
     96                                    self.underlying_set())
    9597       
    9698    def _repr_name_(self):
    9799        return "action"
     
    99101    def actor(self):
    100102        return self.G
    101103   
     104    cdef underlying_set(self):
     105        """
     106        The set on which the actor acts (it is not necessarily the codomain of
     107        the action).
     108
     109        NOTE:
     110
     111        Since this is a cdef'ed method, we can only provide an indirect doctest.
     112
     113        EXAMPLES::
     114
     115            sage: P = QQ['x']
     116            sage: R = (ZZ['x'])['y']
     117            sage: A = R.get_action(P,operator.mul,True)
     118            sage: A                 # indirect doctest
     119            Right scalar multiplication by Univariate Polynomial Ring in x over
     120            Rational Field on Univariate Polynomial Ring in y over Univariate
     121            Polynomial Ring in x over Integer Ring
     122
     123        In this example, the underlying set is the ring ``R``. This is the same
     124        as the left domain, which is different from the codomain of the action::
     125
     126            sage: A.codomain()
     127            Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field
     128            sage: A.codomain() == R
     129            False
     130            sage: A.left_domain() is R
     131            True
     132
     133        By trac ticket #715, there is only a weak reference to the underlying
     134        set. Hence, the underlying set may be garbage collected, even when the
     135        action is still alive. This may result in a runtime error, as follows::
     136
     137            sage: from sage.categories.action import Action
     138            sage: class P: pass
     139            sage: p = P()
     140            sage: q = P()
     141            sage: A = Action(p,q)
     142            sage: A
     143            Left action by <__main__.P instance at ...> on <__main__.P instance at ...>
     144            sage: del q
     145            sage: import gc
     146            sage: n = gc.collect()
     147            sage: A
     148            Traceback (most recent call last):
     149            ...
     150            RuntimeError: This action acted on a set that became garbage collected
     151
     152        """
     153        S = self.S()
     154        if S is None:
     155            raise RuntimeError, "This action acted on a set that became garbage collected"
     156        return S
     157
    102158    def codomain(self):
    103         return self.S
     159        return self.underlying_set()
    104160       
    105161    def domain(self):
    106         return self.S
     162        return self.underlying_set()
    107163       
    108164    def left_domain(self):
    109165        if self._is_left:
     
    145201            # We must be in the case that parent(~a) == parent(a)
    146202            # so we can invert in call_c code below.
    147203            if (PY_TYPE_CHECK(G, Group) and G.is_multiplicative()) or G.is_field():
    148                 Action.__init__(self, G, action.S, action._is_left)
     204                Action.__init__(self, G, action.underlying_set(), action._is_left)
    149205                self._action = action
    150206                return
    151207            else:
    152208                K = G._pseudo_fraction_field()
    153                 Action.__init__(self, K, action.S, action._is_left)
     209                Action.__init__(self, K, action.underlying_set(), action._is_left)
    154210                self._action = action
    155211                return
    156212        except (AttributeError, NotImplementedError):
     
    190246              right_precomposition = homset.Hom(right_precomposition._codomain, right).natural_map() * right_precomposition
    191247            right = right_precomposition._domain
    192248        if action._is_left:
    193             Action.__init__(self, left, action.S, 1)
     249            Action.__init__(self, left, action.underlying_set(), 1)
    194250        else:
    195             Action.__init__(self, right, action.S, 0)
     251            Action.__init__(self, right, action.underlying_set(), 0)
    196252        self._action = action
    197253        self.left_precomposition = left_precomposition
    198254        self.right_precomposition = right_precomposition
     
    230286cdef class ActionEndomorphism(Morphism):
    231287   
    232288    def __init__(self, Action action, g):
    233         Morphism.__init__(self, homset.Hom(action.S, action.S))
     289        Morphism.__init__(self, homset.Hom(action.underlying_set(),
     290                                           action.underlying_set()))
    234291        self._action = action
    235292        self._g = g
    236293       
     
    241298            return self._action._call_(x, self._g)
    242299               
    243300    def _repr_(self):
    244         return "Action of %s on %s under %s."%(self._g, self._action.S, self._action)
     301        return "Action of %s on %s under %s."%(self._g,
     302                                               self._action.underlying_set(), self._action)
    245303       
    246304    def __mul__(left, right):
    247305        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  
    3838       
    3939    def domain(self):
    4040        """
    41         EXAMPLES:
    42             sage: A = MatrixSpace(QQ, 2).get_action(MatrixSpace(ZZ['x'], 2)); A
     41        EXAMPLES:
     42
     43
     44        By trac ticket #715, there only is a weak reference on the underlying set,
     45        so that it can be garbage collected if only the action itself is explicitly
     46        referred to. Hence, we first assign the involved matrix spaces to a
     47        variable::
     48
     49            sage: MSQ = MatrixSpace(QQ, 2)
     50            sage: MSZ = MatrixSpace(ZZ['x'], 2)
     51            sage: A = MSQ.get_action(MSZ); A
    4352            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
    4453            sage: A.actor()
    4554            Full MatrixSpace of 2 by 2 dense matrices over Rational Field
     
    4857            sage: A.codomain()
    4958            Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field
    5059        """
    51         return self.S
     60        return self.underlying_set()
    5261
    5362
    5463cdef class MatrixMatrixAction(MatrixMulAction):
    5564    def __init__(self, G, S):
    5665        """
    57         EXAMPLES:
     66        EXAMPLES:
     67
     68        By trac ticket #715, there only is a weak reference on the underlying set,
     69        so that it can be garbage collected if only the action itself is explicitly
     70        referred to. Hence, we first assign the involved matrix spaces to a
     71        variable::
     72
    5873            sage: R.<x> = ZZ[]
     74            sage: MSR = MatrixSpace(R, 3, 3)
     75            sage: MSQ = MatrixSpace(QQ, 3, 2)
    5976            sage: from sage.matrix.action import MatrixMatrixAction
    60             sage: A = MatrixMatrixAction(MatrixSpace(R, 3, 3), MatrixSpace(QQ, 3, 2)); A
     77            sage: A = MatrixMatrixAction(MSR, MSQ); A
    6178            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
    6279            sage: A.codomain()
    6380            Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field
     
    7289       
    7390    def _create_codomain(self, base):
    7491        """
    75         EXAMPLES:
     92        EXAMPLES:
     93
     94        By trac ticket #715, there only is a weak reference on the underlying set,
     95        so that it can be garbage collected if only the action itself is explicitly
     96        referred to. Hence, we first assign the involved matrix spaces to a
     97        variable::
     98
    7699            sage: from sage.matrix.action import MatrixMatrixAction
    77100            sage: R.<x> = ZZ[]
    78             sage: A = MatrixMatrixAction(MatrixSpace(R, 3, 3), MatrixSpace(QQ, 3, 2)); A
     101            sage: MSR = MatrixSpace(R, 3, 3)
     102            sage: MSQ = MatrixSpace(QQ, 3, 2)
     103            sage: A = MatrixMatrixAction(MSR, MSQ); A
    79104            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
    80105            sage: A.codomain()
    81106            Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field
    82107        """
    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())
     108        if self.G.ncols() != self.underlying_set().nrows():
     109            raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),  self.underlying_set().nrows())
     110        return MatrixSpace(base, self.G.nrows(), self.underlying_set().ncols(), sparse = self.G.is_sparse() and self.underlying_set().is_sparse())
    86111       
    87112    cpdef _call_(self, g, s):
    88113        """
    89114        EXAMPLES:
    90         Respects compatible subdivisions:
     115
     116        Respects compatible subdivisions::
     117
    91118            sage: M = matrix(5, 5, prime_range(100))
    92119            sage: M.subdivide(2,3); M
    93120            [ 2  3  5| 7 11]
     
    112139            [ 8168|11143]
    113140            [11056|15077]
    114141
    115         Note that this is just like block matrix multiplication:
     142        Note that this is just like block matrix multiplication::
     143
    116144            sage: M.subdivision(0,0) * N.subdivision(0,0) + M.subdivision(0,1) * N.subdivision(1,0)
    117145            [1048]
    118146            [3056]
    119147           
    120         If the subdivisions aren't compatible, ignore them.
     148        If the subdivisions aren't compatible, ignore them.
     149        ::
     150
    121151            sage: N.subdivide(1,1); N
    122152            [ 0| 1]
    123153            [--+--]
     
    156186cdef class MatrixVectorAction(MatrixMulAction):
    157187    def __init__(self, G, S):
    158188        """
    159         EXAMPLES:
     189        EXAMPLES::
     190
    160191            sage: from sage.matrix.action import MatrixVectorAction
    161192            sage: A = MatrixVectorAction(MatrixSpace(QQ, 3, 3), VectorSpace(CDF, 4)); A
    162193            Traceback (most recent call last):
     
    176207            sage: A.codomain()
    177208            Vector space of dimension 5 over Complex Double Field
    178209        """
    179         if self.G.ncols() != self.S.degree():
    180             raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),  self.S.degree())
     210        if self.G.ncols() != self.underlying_set().degree():
     211            raise TypeError, "incompatible dimensions %s, %s" % (self.G.ncols(),  self.underlying_set().degree())
    181212        return FreeModule(base, self.G.nrows(), sparse = self.G.is_sparse())
    182213       
    183214    cpdef _call_(self, g, s):
     
    218249            sage: A.codomain()
    219250            Vector space of dimension 5 over Complex Double Field
    220251        """
    221         if self.G.nrows() != self.S.degree():
    222             raise TypeError, "incompatible dimensions %s, %s" % (self.G.nrows(), self.S.degree())
     252        if self.G.nrows() != self.underlying_set().degree():
     253            raise TypeError, "incompatible dimensions %s, %s" % (self.G.nrows(), self.underlying_set().degree())
    223254        return FreeModule(base, self.G.ncols(), sparse = self.G.is_sparse())
    224255       
    225256    cpdef _call_(self, s, g):
  • 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, TripleDictById, TripleDictIter
    88
    99cdef class CoercionModel_cache_maps(CoercionModel):
    1010    # This MUST be a mapping to tuples, where each
    1111    # tuple contains at least two elements that are either
    1212    # None or of type Morphism.
    13     cdef TripleDict _coercion_maps
     13    cdef TripleDictById _coercion_maps
    1414   
    1515    # This MUST be a mapping to actions.
    1616    cdef TripleDict _action_maps
  • sage/structure/coerce.pyx

    diff --git a/sage/structure/coerce.pyx b/sage/structure/coerce.pyx
    a b  
    207207        # This MUST be a mapping of tuples, where each
    208208        # tuple contains at least two elements that are either
    209209        # None or of type Map.
    210         self._coercion_maps = TripleDict(lookup_dict_size, threshold=lookup_dict_threshold)
    211         # This MUST be a mapping to actions. 
    212         self._action_maps = TripleDict(lookup_dict_size, threshold=lookup_dict_threshold)
     210        self._coercion_maps = TripleDictById(lookup_dict_size, threshold=lookup_dict_threshold)
     211        # This MUST be a mapping to actions.
     212        self._action_maps = TripleDictById(lookup_dict_size, threshold=lookup_dict_threshold)
    213213        # This is a mapping from Parents to Parents, storing the result of division in the given parent.
    214214        self._division_parents = TripleDict(lookup_dict_size, threshold=lookup_dict_threshold)
    215215
     
    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"""
     
    12581259                action = PrecomposedAction(action, action.left_domain().coerce_map_from(R), None)
    12591260            if fix and action.right_domain() is not S and action.right_domain() == S:
    12601261                action = PrecomposedAction(action, None, action.right_domain().coerce_map_from(S))
    1261            
     1262
    12621263            if action.left_domain() is not R or action.right_domain() is not S:
    12631264                raise RuntimeError, """There is a BUG in the coercion model:
    12641265                Action found for R %s S does not have the correct domains
  • 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), an_element(self.underlying_set())))
    104104        return self._codomain
    105105
    106106
     
    332332        """
    333333        if self.extended_base is not None:
    334334            return self.extended_base
    335         return self.S
     335        return self.underlying_set()
    336336       
    337337    def domain(self):
    338338        """
     
    345345            sage: A.domain()
    346346            Multivariate Polynomial Ring in x, y, z over Integer Ring
    347347        """
    348         return self.S
     348        return self.underlying_set()
    349349
    350350
    351351
  • sage/structure/coerce_dict.pxd

    diff --git a/sage/structure/coerce_dict.pxd b/sage/structure/coerce_dict.pxd
    a b  
     1cdef class TripleDictEraser
     2
    13cdef class TripleDict:
    24    cdef Py_ssize_t _size
    3     cdef buckets
     5    cdef list key_buckets, ref_buckets
    46    cdef double threshold
    5     cdef get(self, k1, k2, k3)
    6     cdef set(self, k1, k2, k3, value)
    7    
     7    cdef TripleDictEraser eraser
     8    cdef get(self, object k1, object k2, object k3)
     9    cdef set(self, object k1, object k2, object k3, value)
     10
     11cdef class TripleDictById(TripleDict):
     12    pass
     13
    814cdef class TripleDictIter:
    915    cdef TripleDict pairs
    10     cdef buckets, bucket_iter
     16    cdef key_buckets, ref_buckets, key_bucket_iter, ref_bucket_iter
     17
     18cdef class TripleDictEraser:
     19    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#                     2011 Simon King <simon.king@uni-jena.de>
    34#
    45#  Distributed under the terms of the GNU General Public License (GPL)
    56#
     
    89
    910
    1011include "../ext/python_list.pxi"
     12include "../ext/python_ref.pxi"
    1113
     14from sage.misc.constant_function import ConstantFunction
     15
     16############################################
     17# The following code is responsible for
     18# removing dead references from the cache
     19############################################
     20
     21cdef class TripleDictEraser:
     22    """
     23    Erases items from a :class:`TripleDict` when a weak reference becomes invalid.
     24
     25    This is of internal use only. Instances of this class will be passed as a callback
     26    function when creating a weak reference.
     27
     28    EXAMPLES::
     29
     30        sage: from sage.structure.coerce_dict import TripleDict
     31        sage: class A: pass
     32        sage: a = A()
     33        sage: T = TripleDict(11)
     34        sage: T[a,ZZ,None] = 1
     35        sage: T[ZZ,a,1] = 2
     36        sage: T[a,a,ZZ] = 3
     37        sage: len(T)
     38        3
     39        sage: del a
     40        sage: import gc
     41        sage: n = gc.collect()
     42        sage: len(T)
     43        0
     44
     45    AUTHOR:
     46
     47    Simon King (2011-12)
     48    """
     49
     50    def __init__(self, D):
     51        """
     52        INPUT:
     53
     54        A :class:`TripleDict`.
     55
     56        EXAMPLES::
     57
     58            sage: from sage.structure.coerce_dict import TripleDict, TripleDictEraser
     59            sage: D = TripleDict(11)
     60            sage: TripleDictEraser(D)
     61            <sage.structure.coerce_dict.TripleDictEraser object at ...>
     62
     63        """
     64        self.D = D
     65    def __call__(self, r):
     66        """
     67        INPUT:
     68
     69        A weak reference
     70
     71        When this is called with a weak reference ``r``, then each item containing ``r``
     72        is removed from the associated :class:`TripleDict`. Normally, this only happens
     73        when a weak reference becomes invalid.
     74
     75        EXAMPLES::
     76
     77            sage: from sage.structure.coerce_dict import TripleDict
     78            sage: class A: pass
     79            sage: a = A()
     80            sage: T = TripleDict(11)
     81            sage: T[a,ZZ,None] = 1
     82            sage: T[ZZ,a,1] = 2
     83            sage: T[a,a,ZZ] = 3
     84            sage: len(T)
     85            3
     86            sage: del a
     87            sage: import gc
     88            sage: n = gc.collect()
     89            sage: len(T)    # indirect doctest
     90            0
     91
     92        """
     93        # r is a (weak) reference (typically to a parent),
     94        # and it knows the hash of the unique triple r() had been part of.
     95        #
     96        # We remove that unique triple from self.D
     97        cdef int h = r.key % PyList_GET_SIZE(self.D.key_buckets)
     98        cdef list refs = self.D.ref_buckets[h]
     99        cdef list keys = self.D.key_buckets[h]
     100        cdef int i
     101        for i from 0 <= i < PyList_GET_SIZE(refs) by 3:
     102            if refs[i+1] is r or refs[i+2] is r:
     103                del refs[i:i+3]
     104                del keys[i:i+3]
     105                self.D._size -= 1
     106                return
     107
     108from weakref import ref, KeyedRef
    12109
    13110cdef class TripleDict:
    14111    """
     
    20117       - All keys must be sequence of exactly three elements. All sequence
    21118         types (tuple, list, etc.) map to the same item.
    22119       - Comparison is done using the 'is' rather than '==' operator.
    23        
     120
     121    By trac ticket #715, it uses weak references on the first two
     122    items of the key, if they are weakly referenceable. Otherwise, a
     123    :class:`~sage.misc.constant_function.ConstantFunction` is used.
     124
    24125    There are special cdef set/get methods for faster access.
    25126    It is bare-bones in the sense that not all dictionary methods are
    26127    implemented.
    27128   
    28129    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.
     130    is chosen according to the hash of the triple,  and each bucket is of the
     131    form [k1, k2, k3, value, k1, k2, k3, value, ...] on which a linear search
     132    is performed.
    32133   
    33     To spread objects evenly, the size should ideally be a prime, and certainly
    34     not divisible by 2.
     134    To spread objects evenly, the size should ideally be a prime.
    35135   
     136    NOTE:
     137
     138    :class:`TripleDictById` uses comparison by identity ('is') instead
     139    of comparison by '=='.
    36140   
    37     EXAMPLES: 
     141    EXAMPLES::
    38142   
    39143        sage: from sage.structure.coerce_dict import TripleDict
    40144        sage: L = TripleDict(31)
     
    83187        ...
    84188        KeyError: 'a'
    85189       
    86     The following illustrates why even sizes are bad.
    87         sage: L = TripleDict(4, L)
    88         sage: L.stats()
    89         (0, 250.25, 1001)
    90         sage: L.bucket_lens()
    91         [1001, 0, 0, 0]
     190    Here, we show that weak references are indeed in use::
    92191
     192        sage: class A: pass
     193        sage: a = A()
     194        sage: T = TripleDict(11)
     195        sage: T[a,ZZ,None] = 1
     196        sage: T[ZZ,a,1] = 2
     197        sage: T[a,a,ZZ] = 3
     198        sage: len(T)
     199        3
     200        sage: del a
     201        sage: import gc
     202        sage: n = gc.collect()
     203        sage: len(T)
     204        0
    93205
    94     AUTHOR:
    95        -- Robert Bradshaw, 2007-08
     206    We encourage to use objects for the first two constituents of the key
     207    that allow for weak references. However, other objects still work.
     208
     209    AUTHORS:
     210
     211    - Robert Bradshaw, 2007-08
     212    - Simon King, 2011-12
    96213    """
    97214   
    98215    def __init__(self, size, data=None, threshold=0):
    99216        """
    100217        Create a special dict using triples for keys.
    101218       
    102         EXAMPLES:
     219        EXAMPLES::
     220
    103221            sage: from sage.structure.coerce_dict import TripleDict
    104222            sage: L = TripleDict(31)
    105223            sage: a = 'a'; b = 'b'; c = 'c'
     
    109227        """
    110228        cdef int i
    111229        self.threshold = threshold
    112         self.buckets = [[] for i from 0 <= i <  size]
     230        self.key_buckets = [[] for i from 0 <= i <  size]
     231        self.ref_buckets = [[] for i from 0 <= i <  size]
    113232        self._size = 0
     233
     234        self.eraser = TripleDictEraser(self)
    114235        if data is not None:
    115236            for k, v in data.iteritems():
    116237                self[k] = v
     
    119240        """
    120241        The number of items in self.
    121242       
    122         EXAMPLES:
     243        EXAMPLES::
     244
    123245            sage: from sage.structure.coerce_dict import TripleDict
    124246            sage: L = TripleDict(37)
    125247            sage: a = 'a'; b = 'b'; c = 'c'
     
    136258        """
    137259        The distribution of items in buckets.
    138260       
    139         OUTPUT:
    140             (min, avg, max)
     261        OUTPUT:
     262
     263        `(min, avg, max)`
    141264       
    142         EXAMPLES:
     265        EXAMPLES::
     266
    143267            sage: from sage.structure.coerce_dict import TripleDict
    144268            sage: L = TripleDict(37)
    145269            sage: for i in range(100): L[i,i,i] = None
     
    158282        """
    159283        cdef Py_ssize_t size = self._size
    160284        cdef Py_ssize_t cur, min = size, max = 0
    161         for bucket in self.buckets:
     285        for bucket in self.key_buckets:
    162286            if bucket:
    163                 cur = len(bucket)/4
     287                cur = len(bucket)/3
    164288                if cur < min: min = cur
    165289                if cur > max: max = cur
    166290            else:
    167291                min = 0
    168         return min, 1.0*size/len(self.buckets), max
     292        return min, 1.0*size/len(self.key_buckets), max
    169293       
    170294    def bucket_lens(self):
    171295        """
    172296        The distribution of items in buckets.
    173297       
    174         OUTPUT:
    175             A list of how many items are in each bucket.
     298        OUTPUT:
     299
     300        A list of how many items are in each bucket.
    176301       
    177         EXAMPLES:
     302        EXAMPLES::
     303
    178304            sage: from sage.structure.coerce_dict import TripleDict
    179305            sage: L = TripleDict(37)
    180306            sage: for i in range(100): L[i,i,i] = None
     
    188314            sage: L.bucket_lens()
    189315            [100]
    190316        """
    191         return [len(self.buckets[i])/4 for i from 0 <= i < len(self.buckets)]
     317        return [len(self.key_buckets[i])/3 for i from 0 <= i < len(self.key_buckets)]
    192318       
    193319    def _get_buckets(self):
    194320        """
    195321        The actual buckets of self, for debugging.
    196322       
    197         EXAMPLE:
     323        EXAMPLES::
     324
    198325            sage: from sage.structure.coerce_dict import TripleDict
    199326            sage: L = TripleDict(3)
    200327            sage: L[0,0,0] = None
    201328            sage: L._get_buckets() # random
    202329            [[0, 0, 0, None], [], []]
    203330        """
    204         return self.buckets
    205        
     331        return self.key_buckets, self.ref_buckets
     332
     333    ##
     334    # This is the general formalism, equal for all kinds of TripleDicts
     335
     336    def iteritems(self):
     337        """
     338        EXAMPLES::
     339
     340            sage: from sage.structure.coerce_dict import TripleDict
     341            sage: L = TripleDict(31)
     342            sage: L[1,2,3] = None
     343            sage: list(L.iteritems())
     344            [((1, 2, 3), None)]
     345        """
     346        return TripleDictIter(self)
     347
    206348    def __getitem__(self, k):
    207349        """
    208         EXAMPLES:
     350        EXAMPLES::
     351
    209352            sage: from sage.structure.coerce_dict import TripleDict
    210353            sage: L = TripleDict(31)
    211354            sage: a = 'a'; b = 'b'; c = 'c'
     
    213356            sage: L[a,b,c]
    214357            1
    215358        """
     359        cdef object k1,k2,k3
    216360        try:
    217361            k1, k2, k3 = k
    218362        except (TypeError,ValueError):
    219363            raise KeyError, k
    220364        return self.get(k1, k2, k3)
    221365           
    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
    225         cdef Py_ssize_t i
    226         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
    227         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)
    232         raise KeyError, (k1, k2, k3)
    233        
    234366    def __setitem__(self, k, value):
    235367        """
    236         EXAMPLES:
     368        EXAMPLES::
     369
    237370            sage: from sage.structure.coerce_dict import TripleDict
    238371            sage: L = TripleDict(31)
    239372            sage: a = 'a'; b = 'b'; c = 'c'
     
    241374            sage: L[a,b,c]
    242375            -1
    243376        """
     377        cdef object k1,k2,k3
    244378        try:
    245379            k1, k2, k3 = k
    246380        except (TypeError,ValueError):
    247381            raise KeyError, k
    248382        self.set(k1, k2, k3, value)
    249            
    250     cdef set(self, k1, k2, k3, value):
    251         if self.threshold and self._size > len(self.buckets) * self.threshold:
    252             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
    255         cdef Py_ssize_t i
    256         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
    257         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]
    264         self._size += 1
    265            
    266     def __delitem__(self, k):
    267         """
    268         EXAMPLES:
    269             sage: from sage.structure.coerce_dict import TripleDict
    270             sage: L = TripleDict(31)
    271             sage: a = 'a'; b = 'b'; c = 'c'
    272             sage: L[a,b,c] = -1
    273             sage: del L[a,b,c]
    274             sage: len(L)
    275             0
    276         """
    277         try:
    278             k1, k2, k3 = k
    279         except (TypeError,ValueError):
    280             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
    283         cdef Py_ssize_t i
    284         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
    285         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:
    289                 del bucket[i:i+4]
    290                 self._size -= 1
    291                 return
    292         raise KeyError, k
    293        
     383
     384    ########################
     385    # TripleDict and TripleDictById differ in the following methods:
     386
    294387    def resize(self, int buckets=0):
    295388        """
    296389        Changes the number of buckets of self, while preserving the contents.
     
    298391        If the number of buckets is 0 or not given, it resizes self to the
    299392        smallest prime that is at least twice as large as self.
    300393       
    301         EXAMPLES:
     394        EXAMPLES::
     395
    302396            sage: from sage.structure.coerce_dict import TripleDict
    303397            sage: L = TripleDict(8)
    304398            sage: for i in range(100): L[i,i,i] = None
     
    311405            17
    312406        """
    313407        if buckets == 0:
    314             buckets = next_odd_prime(2*len(self.buckets))
    315         cdef TripleDict new = TripleDict(buckets, self)
    316         self.buckets = new.buckets
    317            
    318     def iteritems(self):
     408            buckets = next_odd_prime(2*len(self.key_buckets))
     409        cdef list keys,refs
     410        cdef list key_buckets = self.key_buckets
     411        cdef list ref_buckets = self.ref_buckets
     412        self.key_buckets = [[] for i from 0 <= i < buckets]
     413        self.ref_buckets = [[] for i from 0 <= i < buckets]
     414        cdef Py_ssize_t h
     415        cdef Py_ssize_t i,j
     416        cdef object k1,k2,k3
     417        for j,keys in enumerate(key_buckets):
     418            for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     419                k1,k2,k3 = keys[i:i+3]
     420                h = hash((k1(),k2(),k3)) % buckets
     421                self.key_buckets[h] += [k1,k2,k3]
     422                self.ref_buckets[h] += ref_buckets[j][i:i+3]
     423
     424    cdef get(self, object k1, object k2, object k3):
     425        cdef int h = hash((k1,k2,k3)) % PyList_GET_SIZE(self.key_buckets)
     426        cdef int i
     427        cdef list keys = <list>self.key_buckets[h]
     428        for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     429            if (keys[i+2]==k3) and (keys[i]()==k1) and (keys[i+1]()==k2):
     430                return (<list>self.ref_buckets[h])[i]
     431        raise KeyError, (k1, k2, k3)
     432
     433    cdef set(self, object k1, object k2, object k3, value):
     434        if self.threshold and self._size > len(self.key_buckets) * self.threshold:
     435            self.resize()
     436        cdef long kh = hash((k1,k2,k3))
     437        cdef int h = kh % PyList_GET_SIZE(self.key_buckets)
     438        cdef int i
     439        cdef list keys = self.key_buckets[h]
     440        cdef list refs = self.ref_buckets[h]
     441        for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     442            if (keys[i+2]==k3) and (keys[i]()==k1) and (keys[i+1]()==k2):
     443                refs[i] = value
     444                return
     445        try:
     446            r1 = KeyedRef(k1,self.eraser,kh)
     447        except TypeError:
     448            r1 = ConstantFunction(k1)
     449        try:
     450            r2 = KeyedRef(k2,self.eraser,kh)
     451        except TypeError:
     452            r2 = ConstantFunction(k2)
     453        keys += [r1, r2, k3]
     454        refs += [value, r1, r2]
     455        self._size += 1
     456
     457    def __delitem__(self, k):
    319458        """
    320         EXAMPLES:
     459        EXAMPLES::
     460
    321461            sage: from sage.structure.coerce_dict import TripleDict
    322462            sage: L = TripleDict(31)
    323             sage: L[1,2,3] = None
    324             sage: list(L.iteritems())
    325             [((1, 2, 3), None)]
     463            sage: a = 'a'; b = 'b'; c = 'c'
     464            sage: L[a,b,c] = -1
     465            sage: del L[a,b,c]
     466            sage: len(L)
     467            0
    326468        """
    327         return TripleDictIter(self)
    328        
     469        cdef object k1,k2,k3
     470        try:
     471            k1, k2, k3 = k
     472        except (TypeError,ValueError):
     473            raise KeyError, k
     474        cdef int h = hash((k1,k2,k3)) % PyList_GET_SIZE(self.key_buckets)
     475        cdef int i
     476        cdef list keys = self.key_buckets[h]
     477        cdef list refs = self.ref_buckets[h]
     478        cdef object K1,K2,K3
     479        for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     480            if (keys[i+2]==k3) and (keys[i]()==k1) and (keys[i+1]()==k2):
     481                del keys[i:i+3]
     482                del refs[i:i+3]
     483                self._size -= 1
     484                return
     485        raise KeyError, k
     486
    329487    def __reduce__(self):
    330488        """
    331489        Note that we don't expect equality as this class concerns itself with
    332490        object identity rather than object equality.
    333491
    334         EXAMPLES:
     492        EXAMPLES::
     493
    335494            sage: from sage.structure.coerce_dict import TripleDict
    336495            sage: L = TripleDict(31)
    337496            sage: L[1,2,3] = True
     
    340499            sage: list(loads(dumps(L)).iteritems())
    341500            [((1, 2, 3), True)]
    342501        """
    343         return TripleDict, (len(self.buckets), dict(self.iteritems()), self.threshold)
     502        return TripleDict, (len(self.key_buckets), dict(self.iteritems()), self.threshold)
    344503       
     504cdef class TripleDictById(TripleDict):
     505    """
     506    This is a hashtable specifically designed for (read) speed in
     507    the coercion model.
     508   
     509    It differs from a python dict in the following important ways:
     510   
     511       - All keys must be sequence of exactly three elements. All sequence
     512         types (tuple, list, etc.) map to the same item.
     513       - Comparison is done using the 'is' rather than '==' operator.
     514
     515    By trac ticket #715, it internally uses weak references on the first
     516    two items of the key, if they are weakly referenceable. Otherwise,
     517    a :class:`~sage.misc.constant_function.ConstantFunction` is used.
     518
     519    There are special cdef set/get methods for faster access.
     520    It is bare-bones in the sense that not all dictionary methods are
     521    implemented.
     522   
     523    It is implemented as a list of lists (hereafter called buckets). The bucket
     524    is chosen according to a very simple hash based on the object pointer.
     525    and each bucket is of the form [k1, k2, k3, value, k1, k2, k3, value, ...]
     526    on which a linear search is performed.
     527   
     528    To spread objects evenly, the size should ideally be a prime, and certainly
     529    not divisible by 2.
     530
     531    EXAMPLES::
     532
     533        sage: from sage.structure.coerce_dict import TripleDictById
     534        sage: L = TripleDictById(31)
     535        sage: a = 'a'; b = 'b'; c = 'c'
     536        sage: L[a,b,c] = 1
     537        sage: L[a,b,c]
     538        1
     539        sage: L[c,b,a] = -1
     540        sage: list(L.iteritems())     # random order of output.
     541        [(('c', 'b', 'a'), -1), (('a', 'b', 'c'), 1)]
     542        sage: del L[a,b,c]
     543        sage: list(L.iteritems())
     544        [(('c', 'b', 'a'), -1)]
     545        sage: len(L)
     546        1
     547        sage: L.stats()             # min, avg, max (bucket length)
     548        (0, 0.0322580645161290..., 1)
     549        sage: L.bucket_lens()       # random layout
     550        [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
     551        sage: for i in range(1000):
     552        ...       L[i,i,i] = i
     553        sage: len(L)
     554        1001
     555        sage: L.stats()             # random
     556        (31, 32.29032258064516, 35)
     557        sage: L.bucket_lens()       # random layout
     558        [33, 34, 32, 32, 35, 32, 31, 33, 34, 31, 32, 34, 32, 31, 31, 32, 32, 31, 31, 33, 32, 32, 32, 33, 33, 33, 31, 33, 33, 32, 31]
     559        sage: L = TripleDictById(101, L)
     560        sage: L.stats()             # random
     561        (8, 9.9108910891089117, 11)
     562        sage: L = TripleDictById(3, L)
     563        sage: L.stats()             # random
     564        (291, 333.66666666666669, 410)
     565        sage: L[c,b,a]
     566        -1
     567        sage: L[a,b,c]
     568        Traceback (most recent call last):
     569        ...
     570        KeyError: ('a', 'b', 'c')
     571        sage: L[a]
     572        Traceback (most recent call last):
     573        ...
     574        KeyError: 'a'
     575        sage: L[a] = 1
     576        Traceback (most recent call last):
     577        ...
     578        KeyError: 'a'
     579
     580    Here, we show that weak references are indeed in use::
     581
     582        sage: class A: pass
     583        sage: a = A()
     584        sage: T = TripleDictById(11)
     585        sage: T[a,ZZ,None] = 1
     586        sage: T[ZZ,a,1] = 2
     587        sage: T[a,a,ZZ] = 3
     588        sage: len(T)
     589        3
     590        sage: del a
     591        sage: import gc
     592        sage: n = gc.collect()
     593        sage: len(T)
     594        0
     595
     596    We encourage to use objects for the first two constituents of the key
     597    that allow for weak references. However, other objects still work.
     598    Note that the keys are tested for identity, not for equality::
     599
     600        sage: T[1,1,1] = 1
     601        sage: T[1,1,1]
     602        Traceback (most recent call last):
     603        ...
     604        KeyError: (1, 1, 1)
     605        sage: x = 1
     606        sage: T[x,x,x] = 1
     607        sage: T[x,x,x]
     608        1
     609
     610    The following illustrates why even sizes are bad.
     611    ::
     612
     613        sage: L = TripleDictById(4, L)
     614        sage: L.stats()
     615        (0, 250.25, 1001)
     616        sage: L.bucket_lens()
     617        [1001, 0, 0, 0]
     618
     619    Note that this kind of dictionary is also used for caching actions
     620    and coerce maps. In previous versions of Sage, the cache was by
     621    strong references and resulted in a memory leak in the following
     622    example. However, this leak was fixed by trac ticket #715, using
     623    weak references::
     624
     625        sage: K = GF(1<<55,'t')
     626        sage: for i in range(50):
     627        ...     a = K.random_element()
     628        ...     E = EllipticCurve(j=a)
     629        ...     P = E.random_point()
     630        ...     Q = 2*P
     631        sage: import gc
     632        sage: n = gc.collect()
     633        sage: from sage.schemes.generic.homset import SchemeHomsetModule_abelian_variety_coordinates_field
     634        sage: LE = [x for x in gc.get_objects() if  isinstance(x,SchemeHomsetModule_abelian_variety_coordinates_field)]
     635        sage: len(LE)    # indirect doctest
     636        1
     637
     638    AUTHORS:
     639
     640    - Robert Bradshaw, 2007-08
     641    - Simon King, 2011-12
     642
     643    """
     644    def __reduce__(self):
     645        """
     646        TESTS::
     647
     648            sage: from sage.structure.coerce_dict import TripleDictById
     649            sage: L = TripleDictById(8)
     650            sage: for i in range(100): L[i,i,i] = None
     651            sage: len(loads(dumps(L)))
     652            100
     653
     654        """
     655        return TripleDictById, (len(self.key_buckets), dict(self.iteritems()), self.threshold)
     656
     657    def resize(self, int buckets=0):
     658        """
     659        Changes the number of buckets of self, while preserving the contents.
     660       
     661        If the number of buckets is 0 or not given, it resizes self to the
     662        smallest prime that is at least twice as large as self.
     663       
     664        EXAMPLES::
     665
     666            sage: from sage.structure.coerce_dict import TripleDictById
     667            sage: L = TripleDictById(8)
     668            sage: L[ZZ,QQ,True] = "one value"
     669            sage: for i in range(100): L[i,i,i] = None
     670            sage: L.bucket_lens() # random
     671            [101, 0, 0, 0, 0, 0, 0, 0]
     672            sage: L.resize(7)
     673            sage: L.bucket_lens()   # random
     674            [11, 14, 15, 17, 17, 14, 13]
     675            sage: L.resize()
     676            sage: len(L.bucket_lens())
     677            17
     678            sage: print L[ZZ,QQ,True]
     679            one value
     680
     681        """
     682        if buckets == 0:
     683            buckets = next_odd_prime(2*len(self.key_buckets))
     684        cdef list keys,refs
     685        cdef list key_buckets = self.key_buckets
     686        cdef list ref_buckets = self.ref_buckets
     687        self.key_buckets = [[] for i from 0 <= i < buckets]
     688        self.ref_buckets = [[] for i from 0 <= i < buckets]
     689        cdef Py_ssize_t h, k1,k2
     690        cdef Py_ssize_t i,j
     691        cdef object k3
     692        for j,keys in enumerate(key_buckets):
     693            for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     694                k1,k2,k3 = keys[i:i+3]  # k1 and k2are memory addresses!
     695                h = (k1 + 13*k2^ 503*<Py_ssize_t><void *>k3) % buckets  # That is the new hash
     696                self.key_buckets[h] += [k1,k2,k3]
     697                self.ref_buckets[h] += (<list>ref_buckets[j])[i:i+3]
     698
     699    cdef get(self, object k1, object k2, object k3):
     700        cdef Py_ssize_t h1 = <Py_ssize_t><void *>k1
     701        cdef Py_ssize_t h2 = <Py_ssize_t><void *>k2
     702        cdef int h = (h1 + 13*h2^ 503*<Py_ssize_t><void *>k3) % PyList_GET_SIZE(self.key_buckets)
     703        cdef int i
     704        cdef list keys = <list><object>PyList_GET_ITEM(self.key_buckets,h)
     705        for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     706            if (<Py_ssize_t><object>PyList_GET_ITEM(keys,i)==h1) and \
     707               (<Py_ssize_t><object>PyList_GET_ITEM(keys,i+1)==h2) and \
     708               (PyList_GET_ITEM(keys,i+2)==<PyObject *>k3):
     709                return <object>PyList_GET_ITEM(<object>PyList_GET_ITEM(self.ref_buckets,h),i)
     710        raise KeyError, (k1, k2, k3)
     711
     712    cdef set(self, object k1, object k2, object k3, value):
     713        if self.threshold and self._size > len(self.key_buckets) * self.threshold:
     714            self.resize()
     715        cdef Py_ssize_t h1 = <Py_ssize_t><void *>k1
     716        cdef Py_ssize_t h2 = <Py_ssize_t><void *>k2
     717        cdef long kh = h1 + 13*h2^ 503*<Py_ssize_t><void *>k3
     718        cdef int h = kh % PyList_GET_SIZE(self.key_buckets)
     719        cdef int i
     720        cdef list keys = <list><object>PyList_GET_ITEM(self.key_buckets,h)
     721        cdef list refs = <list><object>PyList_GET_ITEM(self.ref_buckets,h)
     722        for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     723            if (<Py_ssize_t><object>PyList_GET_ITEM(keys,i)==h1) and \
     724               (<Py_ssize_t><object>PyList_GET_ITEM(keys,i+1)==h2) and \
     725               (PyList_GET_ITEM(keys,i+2)==<PyObject *>k3):
     726                refs[i] = value
     727                return
     728        try:
     729            r1 = KeyedRef(k1,self.eraser,kh)
     730        except TypeError:
     731            r1 = ConstantFunction(k1)
     732        try:
     733            r2 = KeyedRef(k2,self.eraser,kh)
     734        except TypeError:
     735            r2 = ConstantFunction(k2)
     736        keys += [h1, h2, k3]
     737        refs += [value, r1, r2]
     738        self._size += 1
     739
     740    def __delitem__(self, k):
     741        """
     742        EXAMPLES::
     743
     744            sage: from sage.structure.coerce_dict import TripleDict
     745            sage: L = TripleDict(31)
     746            sage: a = 'a'; b = 'b'; c = 'c'
     747            sage: L[a,b,c] = -1
     748            sage: del L[a,b,c]
     749            sage: len(L)
     750            0
     751        """
     752        cdef object k1,k2,k3
     753        try:
     754            k1, k2, k3 = k
     755        except (TypeError,ValueError):
     756            raise KeyError, k
     757        cdef Py_ssize_t h1 = <Py_ssize_t><void *>k1
     758        cdef Py_ssize_t h2 = <Py_ssize_t><void *>k2
     759        cdef int h = (h1 + 13*h2^ 503*<Py_ssize_t><void *>k3) % PyList_GET_SIZE(self.key_buckets)
     760        cdef int i
     761        cdef list keys = <list><object>PyList_GET_ITEM(self.key_buckets,h)
     762        cdef list refs = <list><object>PyList_GET_ITEM(self.ref_buckets,h)
     763        for i from 0 <= i < PyList_GET_SIZE(keys) by 3:
     764            if (<Py_ssize_t><object>PyList_GET_ITEM(keys,i)==h1) and \
     765               (<Py_ssize_t><object>PyList_GET_ITEM(keys,i+1)==h2) and \
     766               (PyList_GET_ITEM(keys,i+2)==<PyObject *>k3):
     767                del keys[i:i+3]
     768                del refs[i:i+3]
     769                self._size -= 1
     770                return
     771        raise KeyError, k
    345772
    346773cdef class TripleDictIter:
    347774    def __init__(self, pairs):
     
    354781            ((1, 2, 3), None)
    355782        """
    356783        self.pairs = pairs
    357         self.buckets = iter(self.pairs.buckets)
     784        self.key_buckets = iter(self.pairs.key_buckets)
     785        self.ref_buckets = iter(self.pairs.ref_buckets)
    358786    def __iter__(self):
    359787        """
    360         EXAMPLES:
     788        EXAMPLES::
     789
    361790            sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    362791            sage: L = TripleDict(31)
    363792            sage: L[1,2,3] = None
     
    367796        return self
    368797    def __next__(self):
    369798        """
    370         EXAMPLES:
     799        EXAMPLES::
     800
    371801            sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    372802            sage: L = TripleDict(31)
    373803            sage: L[1,2,3] = None
     
    375805            sage: sorted(L.iteritems())
    376806            [((1, 2, 3), None), ((3, 2, 1), None)]
    377807        """
    378         while self.bucket_iter is None:
    379             self.bucket_iter = self.buckets.next()
    380         self.bucket_iter = iter(self.bucket_iter)
     808        while self.key_bucket_iter is None:
     809            self.key_bucket_iter = self.key_buckets.next()
     810            self.ref_bucket_iter = self.ref_buckets.next()
     811        self.key_bucket_iter = iter(self.key_bucket_iter)
     812        self.ref_bucket_iter = iter(self.ref_bucket_iter)
    381813        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)
     814            k1 = self.key_bucket_iter.next()
     815            k2 = self.key_bucket_iter.next()
     816            k3 = self.key_bucket_iter.next()
     817            value = self.ref_bucket_iter.next()
     818            k1 = self.ref_bucket_iter.next()
     819            k2 = self.ref_bucket_iter.next()
     820            return ((k1(), k2(), k3), value)
    387821        except StopIteration:
    388             self.bucket_iter = None
     822            self.key_bucket_iter = None
     823            self.ref_bucket_iter = None
    389824            return self.next()
    390825
    391826
  • 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 TripleDictById
    1011
    1112cdef class AttributeErrorMessage:
    1213    cdef type cls
     
    7778    # and Parents for which self._rmul_ and/or self._lmul_
    7879    # do the correct thing.
    7980    # Initialized at ring creation.
    80     cdef _action_list
     81    cdef list _action_list
    8182    # Hashtable of everything we've (possibly recursively) discovered so far.
    82     cdef _action_hash
     83    cdef TripleDictById _action_hash
    8384
    8485    # List consisting of Morphisms (from anything to self)
    8586    # 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  
    802802            self._coerce_from_list = []
    803803            self._coerce_from_hash = {}
    804804            self._action_list = []
    805             self._action_hash = {}
     805            self._action_hash = TripleDictById(23)
    806806            self._convert_from_list = []
    807807            self._convert_from_hash = {}
    808808            self._embedding = None
     
    918918        EXAMPLES::
    919919       
    920920            sage: sorted(QQ._introspect_coerce().items())
    921             [('_action_hash', {...}),
     921            [('_action_hash', <sage.structure.coerce_dict.TripleDictById object at ...>),
    922922             ('_action_list', []),
    923923             ('_coerce_from_hash', {...}),
    924924             ('_coerce_from_list', []),
     
    18471847        if isinstance(action, Action):
    18481848            if action.actor() is self:
    18491849                self._action_list.append(action)
    1850                 self._action_hash[action.domain(), action.operation(), action.is_left()] = action
     1850                self._action_hash.set(action.domain(), action.operation(),action.is_left(), action)
    18511851            elif action.domain() is self:
    18521852                self._action_list.append(action)
    1853                 self._action_hash[action.actor(), action.operation(), not action.is_left()] = action
     1853                self._action_hash.set(action.actor(), action.operation(), not action.is_left(), action)
    18541854            else:
    18551855                raise ValueError, "Action must involve self"
    18561856        else:
     
    23862386        try:
    23872387            if self._action_hash is None: # this is because parent.__init__() does not always get called
    23882388                self.init_coerce()
    2389             return self._action_hash[S, op, self_on_left]
     2389            return self._action_hash.get(S, op, self_on_left)
    23902390        except KeyError:
    23912391            pass
    23922392
     
    24012401            # We do NOT add to the list, as this would lead to errors as in
    24022402            # the example above.
    24032403
    2404         self._action_hash[S, op, self_on_left] = action
     2404        self._action_hash.set(S, op, self_on_left, action)
    24052405        return action
    24062406       
    24072407
  • 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 TripleDictById
    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 = TripleDictById(23)
    7071       
    7172        cdef parent.Parent other
    7273        for mor in embeddings: