Ticket #715: trac_715_combined.patch

File trac_715_combined.patch, 48.2 KB (added by SimonKing, 7 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 1326139789 -3600
    # Node ID 80d028a5e1044f9150de27324a15994133d7ac4e
    # Parent  d39176c14b0ee8506f20a0a0ec0ee65ffdc104cd
    #715: 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/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/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.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  
    22    cdef Py_ssize_t _size
    33    cdef buckets
    44    cdef double threshold
    5     cdef get(self, k1, k2, k3)
    6     cdef set(self, k1, k2, k3, value)
     5    cdef TripleDictEraser eraser
     6    cdef get(self, object k1, object k2, object k3)
     7    cdef set(self, object k1, object k2, object k3, value)
    78   
    89cdef class TripleDictIter:
    910    cdef TripleDict pairs
    1011    cdef buckets, bucket_iter
     12
     13cdef class TripleDictEraser:
     14    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#
     
    910
    1011include "../ext/python_list.pxi"
    1112
     13from sage.misc.constant_function import ConstantFunction
     14from weakref import KeyedRef
     15
     16############################################
     17# The following code is responsible for
     18# removing dead references from the cache
     19############################################
     20
     21cdef dict _refcache = {}
     22
     23cdef class TripleDictEraser:
     24    """
     25    Erases items from a :class:`TripleDict` when a weak reference becomes
     26    invalid.
     27
     28    This is of internal use only. Instances of this class will be passed as a
     29    callback function when creating a weak reference.
     30
     31    EXAMPLES::
     32
     33        sage: from sage.structure.coerce_dict import TripleDict
     34        sage: class A: pass
     35        sage: a = A()
     36        sage: T = TripleDict(11)
     37        sage: T[a,ZZ,None] = 1
     38        sage: T[ZZ,a,1] = 2
     39        sage: T[a,a,ZZ] = 3
     40        sage: len(T)
     41        3
     42        sage: del a
     43        sage: import gc
     44        sage: n = gc.collect()
     45        sage: len(T)
     46        0
     47
     48    AUTHOR:
     49
     50    - Simon King (2012-01)
     51    """
     52
     53    def __init__(self, D):
     54        """
     55        INPUT:
     56
     57        A :class:`TripleDict`.
     58
     59        EXAMPLES::
     60
     61            sage: from sage.structure.coerce_dict import TripleDict, TripleDictEraser
     62            sage: D = TripleDict(11)
     63            sage: TripleDictEraser(D)
     64            <sage.structure.coerce_dict.TripleDictEraser object at ...>
     65
     66        """
     67        self.D = D
     68
     69    def __call__(self, r):
     70        """
     71        INPUT:
     72
     73        A weak reference with key.
     74
     75        When this is called with a weak reference ``r``, then each item
     76        containing ``r`` is removed from the associated :class:`TripleDict`.
     77        Normally, this only happens when a weak reference becomes invalid.
     78
     79        EXAMPLES::
     80
     81            sage: from sage.structure.coerce_dict import TripleDict
     82            sage: class A: pass
     83            sage: a = A()
     84            sage: T = TripleDict(11)
     85            sage: T[a,ZZ,None] = 1
     86            sage: T[ZZ,a,1] = 2
     87            sage: T[a,a,ZZ] = 3
     88            sage: len(T)
     89            3
     90            sage: del a
     91            sage: import gc
     92            sage: n = gc.collect()
     93            sage: len(T)    # indirect doctest
     94            0
     95        """
     96        # r is a (weak) reference (typically to a parent), and it knows the
     97        # stored key of the unique triple r() had been part of.
     98        # We remove that unique triple from self.D
     99        cdef size_t k1,k2,k3
     100        k1,k2,k3 = r.key
     101        cdef size_t h = (k1 + 13*k2 ^ 503*k3)
     102        cdef list bucket = <object>PyList_GET_ITEM(self.D.buckets, h % PyList_GET_SIZE(self.D.buckets))
     103        cdef int i
     104        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
     105            if <size_t><object>PyList_GET_ITEM(bucket, i)==k1 and \
     106               <size_t><object>PyList_GET_ITEM(bucket, i+1)==k2 and \
     107               <size_t><object>PyList_GET_ITEM(bucket, i+2)==k3:
     108                del bucket[i:i+4]
     109                self.D._size -= 1
     110                break
     111        cdef list L = _refcache[k1,k2,k3]
     112        del L[L.index(r)]
    12113
    13114cdef class TripleDict:
    14115    """
    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    
     116    This is a hashtable specifically designed for (read) speed in
     117    the coercion model.
     118
     119    It differs from a python dict in the following important ways:
     120
    20121       - All keys must be sequence of exactly three elements. All sequence
    21          types (tuple, list, etc.) map to the same item. 
     122         types (tuple, list, etc.) map to the same item.
    22123       - 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    
     124
     125    There are special cdef set/get methods for faster access.
     126    It is bare-bones in the sense that not all dictionary methods are
     127    implemented.
     128
     129    It is implemented as a list of lists (hereafter called buckets). The bucket
     130    is chosen according to a very simple hash based on the object pointer,
     131    and each bucket is of the form [id(k1), id(k2), id(k3), value, id(k1),
     132    id(k2), id(k3), value, ...], on which a linear search is performed.
     133
    33134    To spread objects evenly, the size should ideally be a prime, and certainly
    34     not divisible by 2.
    35    
    36    
    37     EXAMPLES:
    38    
     135    not divisible by 2.
     136
     137    EXAMPLES::
     138
    39139        sage: from sage.structure.coerce_dict import TripleDict
    40140        sage: L = TripleDict(31)
    41141        sage: a = 'a'; b = 'b'; c = 'c'
     
    82182        Traceback (most recent call last):
    83183        ...
    84184        KeyError: 'a'
    85        
    86     The following illustrates why even sizes are bad.
     185
     186    The following illustrates why even sizes are bad::
     187
    87188        sage: L = TripleDict(4, L)
    88189        sage: L.stats()
    89190        (0, 250.25, 1001)
    90191        sage: L.bucket_lens()
    91192        [1001, 0, 0, 0]
    92193
     194    Note that this kind of dictionary is also used for caching actions
     195    and coerce maps. In previous versions of Sage, the cache was by
     196    strong references and resulted in a memory leak in the following
     197    example. However, this leak was fixed by trac ticket :trac:`715`,
     198    using weak references::
    93199
    94     AUTHOR:
    95        -- Robert Bradshaw, 2007-08
     200        sage: K = GF(1<<55,'t')
     201        sage: for i in range(50):
     202        ...     a = K.random_element()
     203        ...     E = EllipticCurve(j=a)
     204        ...     P = E.random_point()
     205        ...     Q = 2*P
     206        sage: import gc
     207        sage: n = gc.collect()
     208        sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field
     209        sage: LE = [x for x in gc.get_objects() if isinstance(x, EllipticCurve_finite_field)]
     210        sage: len(LE)    # indirect doctest
     211        1
     212
     213    ..NOTE::
     214
     215        The index `h` corresponding to the key [k1, k2, k3] is computed as a
     216        value of unsigned type size_t as follows:
     217
     218        ..MATH::
     219
     220            h = id(k1) + 13*id(k2) \oplus 503 id(k3)
     221
     222        Indeed, although the PyList_GetItem function and corresponding
     223        PyList_GET_ITEM macro take a value of signed type Py_ssize_t as input
     224        for the index, they do not accept negative inputs as the higher level
     225        Python functions. Moreover, the above formula can overflow so that `h`
     226        might be considered as negative. Even though this value is taken
     227        modulo the size of the buckets' list before accessing the corresponding
     228        item, the Cython "%" operator behaves for values of type size_t and
     229        Py_ssize_t like the C "%" operator, rather than like the Python "%"
     230        operator as it does for values of type int. That is, it returns a
     231        result of the same sign as its input. Therefore, if `h` was defined as
     232        a signed value, we might access the list at a negative index and raise
     233        a segfault (and this has been observed on 32 bits systems, see
     234        :trac:`715` for details).
     235
     236    AUTHORS:
     237
     238    - Robert Bradshaw, 2007-08
     239
     240    - Simon King, 2012-01
    96241    """
    97    
     242
    98243    def __init__(self, size, data=None, threshold=0):
    99244        """
    100         Create a special dict using triples for keys.
    101        
    102         EXAMPLES:
     245        Create a special dict using triples for keys.
     246
     247        EXAMPLES::
     248
    103249            sage: from sage.structure.coerce_dict import TripleDict
    104250            sage: L = TripleDict(31)
    105251            sage: a = 'a'; b = 'b'; c = 'c'
     
    111257        self.threshold = threshold
    112258        self.buckets = [[] for i from 0 <= i <  size]
    113259        self._size = 0
     260        self.eraser = TripleDictEraser(self)
    114261        if data is not None:
    115             for k, v in data.iteritems():
    116                 self[k] = v
    117                
     262            for (k1,k2,k3), v in data.iteritems():
     263                self.set(k1,k2,k3, v)
     264
    118265    def __len__(self):
    119266        """
    120267        The number of items in self.
    121        
    122         EXAMPLES:
     268
     269        EXAMPLES::
     270
    123271            sage: from sage.structure.coerce_dict import TripleDict
    124272            sage: L = TripleDict(37)
    125273            sage: a = 'a'; b = 'b'; c = 'c'
     
    131279            3
    132280        """
    133281        return self._size
    134        
     282
    135283    def stats(self):
    136284        """
    137         The distribution of items in buckets.
    138        
    139         OUTPUT:
    140             (min, avg, max)
    141        
    142         EXAMPLES:
     285        The distribution of items in buckets.
     286
     287        OUTPUT:
     288
     289        - (min, avg, max)
     290
     291        EXAMPLES::
     292
    143293            sage: from sage.structure.coerce_dict import TripleDict
    144294            sage: L = TripleDict(37)
    145295            sage: for i in range(100): L[i,i,i] = None
     
    150300            sage: for i in range(100): L[i,i,i] = None
    151301            sage: L.stats() # random
    152302            (0, 0.03325573661456601, 1)
    153            
     303
    154304            sage: L = TripleDict(1)
    155305            sage: for i in range(100): L[i,i,i] = None
    156306            sage: L.stats()
     
    166316            else:
    167317                min = 0
    168318        return min, 1.0*size/len(self.buckets), max
    169        
     319
    170320    def bucket_lens(self):
    171321        """
    172         The distribution of items in buckets.
    173        
    174         OUTPUT:
    175             A list of how many items are in each bucket.
    176        
    177         EXAMPLES:
     322        The distribution of items in buckets.
     323
     324        OUTPUT:
     325
     326        A list of how many items are in each bucket.
     327
     328        EXAMPLES::
     329
    178330            sage: from sage.structure.coerce_dict import TripleDict
    179331            sage: L = TripleDict(37)
    180332            sage: for i in range(100): L[i,i,i] = None
     
    182334            [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]
    183335            sage: sum(L.bucket_lens())
    184336            100
    185            
     337
    186338            sage: L = TripleDict(1)
    187339            sage: for i in range(100): L[i,i,i] = None
    188340            sage: L.bucket_lens()
    189341            [100]
    190342        """
    191343        return [len(self.buckets[i])/4 for i from 0 <= i < len(self.buckets)]
    192        
     344
    193345    def _get_buckets(self):
    194346        """
    195         The actual buckets of self, for debugging.
    196        
    197         EXAMPLE:
     347        The actual buckets of self, for debugging.
     348
     349        EXAMPLES::
     350
    198351            sage: from sage.structure.coerce_dict import TripleDict
    199352            sage: L = TripleDict(3)
    200353            sage: L[0,0,0] = None
     
    202355            [[0, 0, 0, None], [], []]
    203356        """
    204357        return self.buckets
    205        
     358
    206359    def __getitem__(self, k):
    207360        """
    208         EXAMPLES:
     361        EXAMPLES::
     362
    209363            sage: from sage.structure.coerce_dict import TripleDict
    210364            sage: L = TripleDict(31)
    211365            sage: a = 'a'; b = 'b'; c = 'c'
     
    213367            sage: L[a,b,c]
    214368            1
    215369        """
     370        cdef object k1,k2,k3
    216371        try:
    217372            k1, k2, k3 = k
    218373        except (TypeError,ValueError):
    219374            raise KeyError, k
    220375        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
     376
     377    cdef get(self, object k1, object k2, object k3):
     378        cdef size_t h = (<size_t><void *>k1 + 13*<size_t><void *>k2 ^ 503*<size_t><void *>k3)
    225379        cdef Py_ssize_t i
    226         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     380        cdef list all_buckets = self.buckets
     381        cdef list bucket = <object>PyList_GET_ITEM(all_buckets, h % PyList_GET_SIZE(all_buckets))
     382        cdef object tmp
    227383        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)
     384            tmp = <object>PyList_GET_ITEM(bucket, i)
     385            if <size_t>tmp == <size_t><void *>k1:
     386                tmp = <object>PyList_GET_ITEM(bucket, i+1)
     387                if <size_t>tmp == <size_t><void *>k2:
     388                    tmp = <object>PyList_GET_ITEM(bucket, i+2)
     389                    if <size_t>tmp == <size_t><void *>k3:
     390                        return <object>PyList_GET_ITEM(bucket, i+3)
    232391        raise KeyError, (k1, k2, k3)
    233        
     392
    234393    def __setitem__(self, k, value):
    235394        """
    236         EXAMPLES:
     395        EXAMPLES::
     396
    237397            sage: from sage.structure.coerce_dict import TripleDict
    238398            sage: L = TripleDict(31)
    239399            sage: a = 'a'; b = 'b'; c = 'c'
     
    241401            sage: L[a,b,c]
    242402            -1
    243403        """
     404        cdef object k1,k2,k3
    244405        try:
    245406            k1, k2, k3 = k
    246407        except (TypeError,ValueError):
    247408            raise KeyError, k
    248409        self.set(k1, k2, k3, value)
    249            
    250     cdef set(self, k1, k2, k3, value):
     410
     411    cdef set(self, object k1, object k2, object k3, value):
    251412        if self.threshold and self._size > len(self.buckets) * self.threshold:
    252413            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
     414        cdef size_t h1 = <size_t><void *>k1
     415        cdef size_t h2 = <size_t><void *>k2
     416        cdef size_t h3 = <size_t><void *>k3
     417        cdef size_t h = (h1 + 13*h2 ^ 503*h3)
    255418        cdef Py_ssize_t i
    256         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     419        cdef list bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     420        cdef object tmp
    257421        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]
     422            tmp = <object>PyList_GET_ITEM(bucket, i)
     423            if <size_t>tmp == h1:
     424                tmp = <object>PyList_GET_ITEM(bucket, i+1)
     425                if <size_t>tmp == h2:
     426                    tmp = <object>PyList_GET_ITEM(bucket, i+2)
     427                    if <size_t>tmp == h3:
     428                        bucket[i+3] = value
     429                        return
     430        PyList_Append(bucket, h1)
     431        PyList_Append(bucket, h2)
     432        PyList_Append(bucket, h3)
     433        PyList_Append(bucket, value)
     434        try:
     435            PyList_Append(_refcache.setdefault((h1 , h2, h3), []),
     436                KeyedRef(k1,self.eraser,(h1, h2, h3)))
     437        except TypeError:
     438            PyList_Append(_refcache.setdefault((h1, h2, h3), []), k1)
     439        if k2 is not k1:
     440            try:
     441                PyList_Append(_refcache.setdefault((h1 , h2, h3), []),
     442                    KeyedRef(k2,self.eraser,(h1, h2, h3)))
     443            except TypeError:
     444                PyList_Append(_refcache.setdefault((h1, h2, h3), []), k2)
     445        if k3 is not k1 and k3 is not k2:
     446            try:
     447                PyList_Append(_refcache.setdefault((h1 , h2, h3), []),
     448                    KeyedRef(k3,self.eraser,(h1, h2, h3)))
     449            except TypeError:
     450                PyList_Append(_refcache.setdefault((h1, h2, h3), []),k3)
    264451        self._size += 1
    265            
     452
    266453    def __delitem__(self, k):
    267454        """
    268         EXAMPLES:
     455        EXAMPLES::
     456
    269457            sage: from sage.structure.coerce_dict import TripleDict
    270458            sage: L = TripleDict(31)
    271459            sage: a = 'a'; b = 'b'; c = 'c'
     
    274462            sage: len(L)
    275463            0
    276464        """
     465        cdef object k1,k2,k3
    277466        try:
    278467            k1, k2, k3 = k
    279468        except (TypeError,ValueError):
    280469            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
     470        cdef size_t h = (<size_t><void *>k1 + 13*<size_t><void *>k2 ^ 503*<size_t><void *>k3)
    283471        cdef Py_ssize_t i
    284         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     472        cdef list bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
    285473        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:
     474            if <size_t><object>PyList_GET_ITEM(bucket, i) == <size_t><void *>k1 and \
     475               <size_t><object>PyList_GET_ITEM(bucket, i+1) == <size_t><void *>k2 and \
     476               <size_t><object>PyList_GET_ITEM(bucket, i+2) == <size_t><void *>k3:
    289477                del bucket[i:i+4]
    290478                self._size -= 1
    291479                return
    292480        raise KeyError, k
    293        
     481
    294482    def resize(self, int buckets=0):
    295483        """
    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:
     484        Changes the number of buckets of self, while preserving the contents.
     485
     486        If the number of buckets is 0 or not given, it resizes self to the
     487        smallest prime that is at least twice as large as self.
     488
     489        EXAMPLES::
     490
    302491            sage: from sage.structure.coerce_dict import TripleDict
    303492            sage: L = TripleDict(8)
    304493            sage: for i in range(100): L[i,i,i] = None
     
    312501        """
    313502        if buckets == 0:
    314503            buckets = next_odd_prime(2*len(self.buckets))
    315         cdef TripleDict new = TripleDict(buckets, self)
    316         self.buckets = new.buckets
    317            
     504        cdef list old_buckets = self.buckets
     505        cdef list bucket
     506        cdef Py_ssize_t i
     507        cdef size_t h
     508        self.buckets = [[] for i from 0 <= i <  buckets]
     509        cdef size_t k1,k2,k3
     510        cdef object v
     511        for bucket in old_buckets:
     512            for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
     513                k1 = <size_t><object>PyList_GET_ITEM(bucket, i)
     514                k2 = <size_t><object>PyList_GET_ITEM(bucket, i+1)
     515                k3 = <size_t><object>PyList_GET_ITEM(bucket, i+2)
     516                v  = <object>PyList_GET_ITEM(bucket, i+3)
     517                h = (k1 + 13*k2 ^ 503*k3)
     518                self.buckets[h % buckets] += [k1,k2,k3,v]
     519
    318520    def iteritems(self):
    319521        """
    320         EXAMPLES:
     522        EXAMPLES::
     523
    321524            sage: from sage.structure.coerce_dict import TripleDict
    322525            sage: L = TripleDict(31)
    323526            sage: L[1,2,3] = None
     
    325528            [((1, 2, 3), None)]
    326529        """
    327530        return TripleDictIter(self)
    328        
     531
    329532    def __reduce__(self):
    330533        """
    331         Note that we don't expect equality as this class concerns itself with 
    332         object identity rather than object equality. 
     534        Note that we don't expect equality as this class concerns itself with
     535        object identity rather than object equality.
    333536
    334         EXAMPLES:
     537        EXAMPLES::
     538
    335539            sage: from sage.structure.coerce_dict import TripleDict
    336540            sage: L = TripleDict(31)
    337541            sage: L[1,2,3] = True
     
    341545            [((1, 2, 3), True)]
    342546        """
    343547        return TripleDict, (len(self.buckets), dict(self.iteritems()), self.threshold)
    344        
    345548
    346549cdef class TripleDictIter:
    347550    def __init__(self, pairs):
    348551        """
    349         EXAMPLES:
     552        EXAMPLES::
     553
    350554            sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    351555            sage: L = TripleDict(31)
    352556            sage: L[1,2,3] = None
     
    355559        """
    356560        self.pairs = pairs
    357561        self.buckets = iter(self.pairs.buckets)
     562
    358563    def __iter__(self):
    359564        """
    360         EXAMPLES:
     565        EXAMPLES::
     566
    361567            sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    362568            sage: L = TripleDict(31)
    363569            sage: L[1,2,3] = None
     
    365571            ((1, 2, 3), None)
    366572        """
    367573        return self
     574
    368575    def __next__(self):
    369576        """
    370         EXAMPLES:
     577        EXAMPLES::
     578
    371579            sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
    372580            sage: L = TripleDict(31)
    373581            sage: L[1,2,3] = None
     
    378586        while self.bucket_iter is None:
    379587            self.bucket_iter = self.buckets.next()
    380588        self.bucket_iter = iter(self.bucket_iter)
     589        cdef size_t k1,k2,k3
    381590        try:
    382591            k1 = self.bucket_iter.next()
    383592            k2 = self.bucket_iter.next()
    384593            k3 = self.bucket_iter.next()
    385594            value = self.bucket_iter.next()
    386             return ((k1, k2, k3), value)
     595            return ((<object><PyObject *>k1, <object><PyObject *>k2,
     596                     <object><PyObject *>k3), value)
    387597        except StopIteration:
    388598            self.bucket_iter = None
    389599            return self.next()
    390600
    391601
    392 
    393602cdef long next_odd_prime(long n):
    394603    if n % 2 == 0:
    395604        n -= 1
  • 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: