Ticket #715: trac715_weak_action.patch

File trac715_weak_action.patch, 21.0 KB (added by SimonKing, 8 years ago)

Use weak references to the underlying set of an action. Use TripleDictById to store actions in parents. Disregard the orphan_functor patch!

  • sage/categories/action.pxd

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1325708162 -3600
    # Node ID 7a5d8cfc52324c295ca4050e45a28dc2d06333a9
    # Parent  634489af2f27ac285708c991309b0dd5d38f9854
    #715: 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/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/structure/coerce.pyx

    diff --git a/sage/structure/coerce.pyx b/sage/structure/coerce.pyx
    a b  
    9191import sys, traceback
    9292
    9393from coerce_actions import LeftModuleAction, RightModuleAction, IntegerMulAction
    94 from weakref import ref
    95 from sage.misc.constant_function import ConstantFunction
    96 NoneFunction = ConstantFunction(False)
    9794
    9895cpdef py_scalar_parent(py_type):
    9996    """
     
    12081205
    12091206        """
    12101207        try:
    1211             action = self._action_maps.get(R, S, op)()
    1212             if action is False:
    1213                 return None
    1214             if action is not None:
    1215                 return action
     1208            return self._action_maps.get(R, S, op)
    12161209        except KeyError:
    12171210            pass
    12181211        action = self.discover_action(R, S, op)
    12191212        action = self.verify_action(action, R, S, op)
    1220         if action is None:
    1221             self._action_maps.set(R, S, op, NoneFunction)
    1222         else:
    1223             self._action_maps.set(R, S, op, ref(action))
     1213        self._action_maps.set(R, S, op, action)
    12241214        return action
    12251215           
    12261216    cpdef verify_action(self, action, R, S, op, bint fix=True):
     
    12691259                action = PrecomposedAction(action, action.left_domain().coerce_map_from(R), None)
    12701260            if fix and action.right_domain() is not S and action.right_domain() == S:
    12711261                action = PrecomposedAction(action, None, action.right_domain().coerce_map_from(S))
    1272            
     1262
    12731263            if action.left_domain() is not R or action.right_domain() is not S:
    12741264                raise RuntimeError, """There is a BUG in the coercion model:
    12751265                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.pyx

    diff --git a/sage/structure/coerce_dict.pyx b/sage/structure/coerce_dict.pyx
    a b  
    128128   
    129129    To spread objects evenly, the size should ideally be a prime.
    130130   
     131    NOTE:
     132
     133    :class:`TripleDictById` uses comparison by identity ('is') instead
     134    of comparison by '=='.
    131135   
    132136    EXAMPLES::
    133137   
     
    518522        sage: len(L)
    519523        1
    520524        sage: L.stats()             # min, avg, max (bucket length)
    521         (0, 0.032258064516129031, 1)
     525        (0, 0.0322580645161290..., 1)
    522526        sage: L.bucket_lens()       # random layout
    523527        [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]
    524528        sage: for i in range(1000):
     
    589593        sage: L.bucket_lens()
    590594        [1001, 0, 0, 0]
    591595
     596    Note that this kind of dictionary is also used for caching actions
     597    and coerce maps. In previous versions of Sage, the cache was by
     598    strong references and resulted in a memory leak in the following
     599    example. However, this leak was fixed by trac ticket #715, using
     600    weak references::
     601
     602        sage: K = GF(1<<55,'t')
     603        sage: for i in range(50):
     604        ...     a = K.random_element()
     605        ...     E = EllipticCurve(j=a)
     606        ...     P = E.random_point()
     607        ...     Q = 2*P
     608        sage: import gc
     609        sage: n = gc.collect()
     610        sage: from sage.schemes.generic.homset import SchemeHomsetModule_abelian_variety_coordinates_field
     611        sage: LE = [x for x in gc.get_objects() if  isinstance(x,SchemeHomsetModule_abelian_variety_coordinates_field)]
     612        sage: len(LE)    # indirect doctest
     613        1
     614
    592615    AUTHORS:
    593616
    594617    - Robert Bradshaw, 2007-08
  • 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: