Ticket #715: trac715_weak_action.patch
File trac715_weak_action.patch, 21.0 KB (added by , 8 years ago) |
---|
-
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 8 8 cdef S 9 9 cdef bint _is_left 10 10 cdef op 11 cdef underlying_set(self) 11 12 cpdef _call_(self, a, b) 12 13 13 14 -
sage/categories/action.pyx
diff --git a/sage/categories/action.pyx b/sage/categories/action.pyx
a b 32 32 33 33 import homset 34 34 import sage.structure.element 35 from weakref import ref 35 36 36 37 include "../ext/stdsage.pxi" 37 38 … … 48 49 from groupoid import Groupoid 49 50 Functor.__init__(self, Groupoid(G), category(S)) 50 51 self.G = G 51 self.S = S52 self.S = ref(S) 52 53 self._is_left = is_left 53 54 self.op = op 54 55 … … 61 62 if g in self.G: 62 63 return ActionEndomorphism(self, self.G(g)) 63 64 elif g == self.G: 64 return self. S65 return self.underlying_set() 65 66 else: 66 67 raise TypeError, "%s not an element of %s"%(g, self.G) 67 68 elif len(args) == 2: 68 69 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])) 70 71 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])) 72 73 73 74 cpdef _call_(self, a, b): 74 75 raise NotImplementedError, "Action not implemented." … … 91 92 92 93 def __repr__(self): 93 94 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()) 95 97 96 98 def _repr_name_(self): 97 99 return "action" … … 99 101 def actor(self): 100 102 return self.G 101 103 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 102 158 def codomain(self): 103 return self. S159 return self.underlying_set() 104 160 105 161 def domain(self): 106 return self. S162 return self.underlying_set() 107 163 108 164 def left_domain(self): 109 165 if self._is_left: … … 145 201 # We must be in the case that parent(~a) == parent(a) 146 202 # so we can invert in call_c code below. 147 203 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) 149 205 self._action = action 150 206 return 151 207 else: 152 208 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) 154 210 self._action = action 155 211 return 156 212 except (AttributeError, NotImplementedError): … … 190 246 right_precomposition = homset.Hom(right_precomposition._codomain, right).natural_map() * right_precomposition 191 247 right = right_precomposition._domain 192 248 if action._is_left: 193 Action.__init__(self, left, action. S, 1)249 Action.__init__(self, left, action.underlying_set(), 1) 194 250 else: 195 Action.__init__(self, right, action. S, 0)251 Action.__init__(self, right, action.underlying_set(), 0) 196 252 self._action = action 197 253 self.left_precomposition = left_precomposition 198 254 self.right_precomposition = right_precomposition … … 230 286 cdef class ActionEndomorphism(Morphism): 231 287 232 288 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())) 234 291 self._action = action 235 292 self._g = g 236 293 … … 241 298 return self._action._call_(x, self._g) 242 299 243 300 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) 245 303 246 304 def __mul__(left, right): 247 305 cdef ActionEndomorphism left_c, right_c -
sage/matrix/action.pyx
diff --git a/sage/matrix/action.pyx b/sage/matrix/action.pyx
a b 38 38 39 39 def domain(self): 40 40 """ 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 43 52 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 44 53 sage: A.actor() 45 54 Full MatrixSpace of 2 by 2 dense matrices over Rational Field … … 48 57 sage: A.codomain() 49 58 Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field 50 59 """ 51 return self. S60 return self.underlying_set() 52 61 53 62 54 63 cdef class MatrixMatrixAction(MatrixMulAction): 55 64 def __init__(self, G, S): 56 65 """ 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 58 73 sage: R.<x> = ZZ[] 74 sage: MSR = MatrixSpace(R, 3, 3) 75 sage: MSQ = MatrixSpace(QQ, 3, 2) 59 76 sage: from sage.matrix.action import MatrixMatrixAction 60 sage: A = MatrixMatrixAction(M atrixSpace(R, 3, 3), MatrixSpace(QQ, 3, 2)); A77 sage: A = MatrixMatrixAction(MSR, MSQ); A 61 78 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 62 79 sage: A.codomain() 63 80 Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field … … 72 89 73 90 def _create_codomain(self, base): 74 91 """ 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 76 99 sage: from sage.matrix.action import MatrixMatrixAction 77 100 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 79 104 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 80 105 sage: A.codomain() 81 106 Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field 82 107 """ 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()) 86 111 87 112 cpdef _call_(self, g, s): 88 113 """ 89 114 EXAMPLES: 90 Respects compatible subdivisions: 115 116 Respects compatible subdivisions:: 117 91 118 sage: M = matrix(5, 5, prime_range(100)) 92 119 sage: M.subdivide(2,3); M 93 120 [ 2 3 5| 7 11] … … 112 139 [ 8168|11143] 113 140 [11056|15077] 114 141 115 Note that this is just like block matrix multiplication: 142 Note that this is just like block matrix multiplication:: 143 116 144 sage: M.subdivision(0,0) * N.subdivision(0,0) + M.subdivision(0,1) * N.subdivision(1,0) 117 145 [1048] 118 146 [3056] 119 147 120 If the subdivisions aren't compatible, ignore them. 148 If the subdivisions aren't compatible, ignore them. 149 :: 150 121 151 sage: N.subdivide(1,1); N 122 152 [ 0| 1] 123 153 [--+--] … … 156 186 cdef class MatrixVectorAction(MatrixMulAction): 157 187 def __init__(self, G, S): 158 188 """ 159 EXAMPLES: 189 EXAMPLES:: 190 160 191 sage: from sage.matrix.action import MatrixVectorAction 161 192 sage: A = MatrixVectorAction(MatrixSpace(QQ, 3, 3), VectorSpace(CDF, 4)); A 162 193 Traceback (most recent call last): … … 176 207 sage: A.codomain() 177 208 Vector space of dimension 5 over Complex Double Field 178 209 """ 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()) 181 212 return FreeModule(base, self.G.nrows(), sparse = self.G.is_sparse()) 182 213 183 214 cpdef _call_(self, g, s): … … 218 249 sage: A.codomain() 219 250 Vector space of dimension 5 over Complex Double Field 220 251 """ 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()) 223 254 return FreeModule(base, self.G.ncols(), sparse = self.G.is_sparse()) 224 255 225 256 cpdef _call_(self, s, g): -
sage/structure/coerce.pyx
diff --git a/sage/structure/coerce.pyx b/sage/structure/coerce.pyx
a b 91 91 import sys, traceback 92 92 93 93 from coerce_actions import LeftModuleAction, RightModuleAction, IntegerMulAction 94 from weakref import ref95 from sage.misc.constant_function import ConstantFunction96 NoneFunction = ConstantFunction(False)97 94 98 95 cpdef py_scalar_parent(py_type): 99 96 """ … … 1208 1205 1209 1206 """ 1210 1207 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) 1216 1209 except KeyError: 1217 1210 pass 1218 1211 action = self.discover_action(R, S, op) 1219 1212 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) 1224 1214 return action 1225 1215 1226 1216 cpdef verify_action(self, action, R, S, op, bint fix=True): … … 1269 1259 action = PrecomposedAction(action, action.left_domain().coerce_map_from(R), None) 1270 1260 if fix and action.right_domain() is not S and action.right_domain() == S: 1271 1261 action = PrecomposedAction(action, None, action.right_domain().coerce_map_from(S)) 1272 1262 1273 1263 if action.left_domain() is not R or action.right_domain() is not S: 1274 1264 raise RuntimeError, """There is a BUG in the coercion model: 1275 1265 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 100 100 Multivariate Polynomial Ring in x, y, z over Rational Field 101 101 """ 102 102 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()))) 104 104 return self._codomain 105 105 106 106 … … 332 332 """ 333 333 if self.extended_base is not None: 334 334 return self.extended_base 335 return self. S335 return self.underlying_set() 336 336 337 337 def domain(self): 338 338 """ … … 345 345 sage: A.domain() 346 346 Multivariate Polynomial Ring in x, y, z over Integer Ring 347 347 """ 348 return self. S348 return self.underlying_set() 349 349 350 350 351 351 -
sage/structure/coerce_dict.pyx
diff --git a/sage/structure/coerce_dict.pyx b/sage/structure/coerce_dict.pyx
a b 128 128 129 129 To spread objects evenly, the size should ideally be a prime. 130 130 131 NOTE: 132 133 :class:`TripleDictById` uses comparison by identity ('is') instead 134 of comparison by '=='. 131 135 132 136 EXAMPLES:: 133 137 … … 518 522 sage: len(L) 519 523 1 520 524 sage: L.stats() # min, avg, max (bucket length) 521 (0, 0.0322580645161290 31, 1)525 (0, 0.0322580645161290..., 1) 522 526 sage: L.bucket_lens() # random layout 523 527 [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] 524 528 sage: for i in range(1000): … … 589 593 sage: L.bucket_lens() 590 594 [1001, 0, 0, 0] 591 595 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 592 615 AUTHORS: 593 616 594 617 - Robert Bradshaw, 2007-08 -
sage/structure/parent.pxd
diff --git a/sage/structure/parent.pxd b/sage/structure/parent.pxd
a b 7 7 ############################################################################### 8 8 9 9 cimport sage.structure.category_object 10 from sage.structure.coerce_dict cimport TripleDictById 10 11 11 12 cdef class AttributeErrorMessage: 12 13 cdef type cls … … 77 78 # and Parents for which self._rmul_ and/or self._lmul_ 78 79 # do the correct thing. 79 80 # Initialized at ring creation. 80 cdef _action_list81 cdef list _action_list 81 82 # Hashtable of everything we've (possibly recursively) discovered so far. 82 cdef _action_hash83 cdef TripleDictById _action_hash 83 84 84 85 # List consisting of Morphisms (from anything to self) 85 86 # 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 802 802 self._coerce_from_list = [] 803 803 self._coerce_from_hash = {} 804 804 self._action_list = [] 805 self._action_hash = {}805 self._action_hash = TripleDictById(23) 806 806 self._convert_from_list = [] 807 807 self._convert_from_hash = {} 808 808 self._embedding = None … … 918 918 EXAMPLES:: 919 919 920 920 sage: sorted(QQ._introspect_coerce().items()) 921 [('_action_hash', {...}),921 [('_action_hash', <sage.structure.coerce_dict.TripleDictById object at ...>), 922 922 ('_action_list', []), 923 923 ('_coerce_from_hash', {...}), 924 924 ('_coerce_from_list', []), … … 1847 1847 if isinstance(action, Action): 1848 1848 if action.actor() is self: 1849 1849 self._action_list.append(action) 1850 self._action_hash [action.domain(), action.operation(), action.is_left()] = action1850 self._action_hash.set(action.domain(), action.operation(),action.is_left(), action) 1851 1851 elif action.domain() is self: 1852 1852 self._action_list.append(action) 1853 self._action_hash [action.actor(), action.operation(), not action.is_left()] = action1853 self._action_hash.set(action.actor(), action.operation(), not action.is_left(), action) 1854 1854 else: 1855 1855 raise ValueError, "Action must involve self" 1856 1856 else: … … 2386 2386 try: 2387 2387 if self._action_hash is None: # this is because parent.__init__() does not always get called 2388 2388 self.init_coerce() 2389 return self._action_hash [S, op, self_on_left]2389 return self._action_hash.get(S, op, self_on_left) 2390 2390 except KeyError: 2391 2391 pass 2392 2392 … … 2401 2401 # We do NOT add to the list, as this would lead to errors as in 2402 2402 # the example above. 2403 2403 2404 self._action_hash [S, op, self_on_left] = action2404 self._action_hash.set(S, op, self_on_left, action) 2405 2405 return action 2406 2406 2407 2407 -
sage/structure/parent_old.pyx
diff --git a/sage/structure/parent_old.pyx b/sage/structure/parent_old.pyx
a b 30 30 import operator 31 31 from parent import Set_PythonType, Set_PythonType_class 32 32 from coerce import py_scalar_parent 33 from sage.structure.coerce_dict import TripleDictById 33 34 34 35 include '../ext/python_object.pxi' 35 36 include '../ext/python_bool.pxi' … … 66 67 self._coerce_from_list = list(coerce_from) 67 68 self._coerce_from_hash = {} 68 69 self._action_list = list(actions) 69 self._action_hash = {}70 self._action_hash = TripleDictById(23) 70 71 71 72 cdef parent.Parent other 72 73 for mor in embeddings: