# HG changeset patch
# User Simon King
# Date 1324923418 3600
# Node ID 10a4774934582e834dd882128d60df651fc3f476
# Parent 9ca30e8d246cd318261c3e9f46bd351d119fdff5
#11521: Use a more sophisticated way of using weak references in the homset cache
If an object of a category does not support weak references, then the error message
suggests to use CategoryObject for implementing category objects.
diff git a/sage/categories/homset.py b/sage/categories/homset.py
 a/sage/categories/homset.py
+++ b/sage/categories/homset.py
@@ 10,6 +10,8 @@
 William Stein (20060114): Changed from Homspace to Homset.
 Nicolas M. Thiery (200812): Updated for the new category framework
+
+ Simon King (201112): Use a weak cache for homsets
"""
#*****************************************************************************
@@ 30,13 +32,14 @@
import weakref
from sage.categories.category import Category
+from sage.structure.category_object import CategoryObject
import morphism
from sage.structure.parent import Parent, Set_generic
from sage.misc.lazy_attribute import lazy_attribute
from sage.misc.cachefunc import cached_function
import types
_cache = {}
+_cache = weakref.WeakKeyDictionary()
def Hom(X, Y, category=None):
"""
Create the space of homomorphisms from X to Y in the category ``category``.
@@ 72,6 +75,21 @@
sage: Hom(FreeModule(QQ,1), FreeModule(ZZ,1))
Set of Morphisms from Vector space of dimension 1 over Rational Field to Ambient free module of rank 1 over the principal ideal domain Integer Ring in Category of vector spaces over Rational Field
+ Here, we test against a memory leak that has been fixed at #11521 by using
+ a weak cache::
+
+ sage: for p in prime_range(10^5):
+ ... K = GF(p)
+ ... a = K(0)
+ sage: import gc
+ sage: gc.collect() # random
+ 624
+ sage: from sage.rings.finite_rings.finite_field_prime_modn import FiniteField_prime_modn as FF
+ sage: L = [x for x in gc.get_objects() if isinstance(x, FF)]
+ sage: len(L), L[0], L[len(L)1]
+ (2, Finite Field of size 2, Finite Field of size 99991)
+
+
To illustrate the choice of the category, we consider the
following parents as running examples::
@@ 155,17 +173,38 @@
# However it breaks somehow the coercion (see e.g. sage t sage.rings.real_mpfr)
# To be investigated.
global _cache
 key = (X,Y,category)
 if _cache.has_key(key):
 H = _cache[key]()
 # What is this test for? Why does the cache ever contain a 0 value?
 # This actually occurs: see e.g. sage t sagecombinat/sage/categories/modules_with_basis.py
 if H:
 # Are domain or codomain breaking the unique parent condition?
 if H.domain() is X and H.codomain() is Y:
 return H
+ try:
+ cache2 = _cache[X]
+ except KeyError:
+ cache2 = _cache[X] = weakref.WeakKeyDictionary()
+ except TypeError:
+ raise TypeError, "Objects of categories must be instances of %s, but %s isn't"%(CategoryObject,X)
try:
+ cache3 = cache2[Y]
+ except KeyError:
+ # It would not be reasonable to use a WeakValueDictionary here,
+ # since the homset has a reference to the key anyway. Hence, if
+ # the value could be garbage collected, then the key could be
+ # garbage collected as well.
+ cache3 = cache2[Y] = {}
+ except TypeError:
+ raise TypeError, "Objects of categories must be instances of %s, but %s isn't"%(CategoryObject,Y)
+
+ catkey = weakref.ref(category) if category is not None else None
+ try:
+ H = cache3[catkey]
+ except KeyError:
+ H = None
+ # What is this test for? Why does the cache ever contain a 0 value?
+ # This actually occurs: see e.g. sage t sagecombinat/sage/categories/modules_with_basis.py
+ if H:
+ # Are domain or codomain breaking the unique parent condition?
+ if H.domain() is X and H.codomain() is Y:
+ return H
+
+ try:
+ # apparently _Hom_ is supposed to be cached.
return X._Hom_(Y, category)
except (AttributeError, TypeError):
pass
@@ 182,13 +221,16 @@
else:
raise TypeError, "Argument category (= %s) must be a category."%category
# Now, as the category may have changed, we try to find the hom set in the cache, again:
 key = (X,Y,category)
 if _cache.has_key(key):
 H = _cache[key]()
 if H:
 # Are domain or codomain breaking the unique parent condition?
 if H.domain() is X and H.codomain() is Y:
 return H
+ catkey = weakref.ref(category) if category is not None else None
+ try:
+ H = cache3[catkey]
+ except KeyError:
+ pass
+
+ if H:
+ # Are domain or codomain breaking the unique parent condition?
+ if H.domain() is X and H.codomain() is Y:
+ return H
# coercing would be incredibly annoying, since the domain and codomain
# are totally different objects
@@ 201,7 +243,7 @@
H = category.hom_category().parent_class(X, Y, category = category)
##_cache[key] = weakref.ref(H)
 _cache[(X, Y, category)] = weakref.ref(H)
+ cache3[catkey] = H
return H
def hom(X, Y, f):
diff git a/sage/rings/polynomial/multi_polynomial_ring.py b/sage/rings/polynomial/multi_polynomial_ring.py
 a/sage/rings/polynomial/multi_polynomial_ring.py
+++ b/sage/rings/polynomial/multi_polynomial_ring.py
@@ 79,6 +79,8 @@
from sage.rings.polynomial.polydict import PolyDict, ETuple
from sage.rings.polynomial.term_order import TermOrder
+from sage.structure.parent import Parent
+
from sage.interfaces.all import is_SingularElement
from sage.interfaces.all import macaulay2 as macaulay2_default
from sage.interfaces.macaulay2 import is_Macaulay2Element
@@ 137,7 +139,7 @@
def __init__(self, base_ring, n, names, order):
from sage.rings.polynomial.polynomial_singular_interface import can_convert_to_singular
order = TermOrder(order,n)
 MPolynomialRing_generic.__init__(self, base_ring, n, names, order)
+ Parent.__init__(self, base=base_ring)
# Construct the generators
v = [0 for _ in xrange(n)]
one = base_ring(1);
@@ 148,6 +150,7 @@
self._gens.append(C(self, {tuple(v):one}))
v[i] = 0
self._gens = tuple(self._gens)
+ MPolynomialRing_generic.__init__(self, base_ring, n, names, order)
self._zero_tuple = tuple(v)
self._has_singular = can_convert_to_singular(self)
# This polynomial ring should belong to Algebras(base_ring).
diff git a/sage/structure/category_object.pyx b/sage/structure/category_object.pyx
 a/sage/structure/category_object.pyx
+++ b/sage/structure/category_object.pyx
@@ 583,14 +583,21 @@
sage: R.Hom(QQ)
Set of Homomorphisms from Multivariate Polynomial Ring in x, y over Rational Field to Rational Field
 Homspaces are defined for very general Sage objects, even elements of familiar rings.
+ Homspaces are defined for very general Sage objects. However, since trac
+ ticket #11521, it is enforced that objects of categories support weak
+ references (which is the case if they are instances of
+ :class:`~sage.structure.category_object.CategoryObject`.
+
+ ::
 ::

sage: n = 5; Hom(n,7)
 Set of Morphisms from 5 to 7 in Category of elements of Integer Ring
 sage: z=(2/3); Hom(z,8/1)
 Set of Morphisms from 2/3 to 8 in Category of elements of Rational Field
+ Traceback (most recent call last):
+ ...
+ TypeError: Objects of categories must be instances of , but 5 isn't
+ sage: Hom(ZZ,8/1)
+ Traceback (most recent call last):
+ ...
+ TypeError: Objects of categories must be instances of , but 8 isn't
This example illustrates the optional third argument::
diff git a/sage/structure/parent.pyx b/sage/structure/parent.pyx
 a/sage/structure/parent.pyx
+++ b/sage/structure/parent.pyx
@@ 1415,14 +1415,21 @@
sage: R.Hom(QQ)
Set of Homomorphisms from Multivariate Polynomial Ring in x, y over Rational Field to Rational Field
 Homspaces are defined for very general Sage objects, even elements of familiar rings.
+ Homspaces are defined for very general Sage objects. However, since trac
+ ticket #11521, it is enforced that objects of categories support weak
+ references (which is the case if they are instances of
+ :class:`~sage.structure.category_object.CategoryObject`.
::
sage: n = 5; Hom(n,7)
 Set of Morphisms from 5 to 7 in Category of elements of Integer Ring
+ Traceback (most recent call last):
+ ...
+ TypeError: Objects of categories must be instances of , but 5 isn't
sage: z=(2/3); Hom(z,8/1)
 Set of Morphisms from 2/3 to 8 in Category of elements of Rational Field
+ Traceback (most recent call last):
+ ...
+ TypeError: Objects of categories must be instances of , but 2/3 isn't
This example illustrates the optional third argument::
diff git a/sage/structure/parent_base.pyx b/sage/structure/parent_base.pyx
 a/sage/structure/parent_base.pyx
+++ b/sage/structure/parent_base.pyx
@@ 99,18 +99,30 @@
homomorphisms from self to codomain in the category cat. The
default category is \code{self.category()}.
 EXAMPLES:
+ EXAMPLES::
+
sage: R. = PolynomialRing(QQ, 2)
sage: R.Hom(QQ)
Set of Homomorphisms from Multivariate Polynomial Ring in x, y over Rational Field to Rational Field
 Homspaces are defined for very general \sage objects, even elements of familiar rings.
+ Homspaces are defined for very general Sage objects. However, since trac
+ ticket #11521, it is enforced that objects of categories support weak
+ references (which is the case if they are instances of
+ :class:`~sage.structure.category_object.CategoryObject`.
+
+ ::
+
sage: n = 5; Hom(n,7)
 Set of Morphisms from 5 to 7 in Category of elements of Integer Ring
+ Traceback (most recent call last):
+ ...
+ TypeError: Objects of categories must be instances of , but 5 isn't
sage: z=(2/3); Hom(z,8/1)
 Set of Morphisms from 2/3 to 8 in Category of elements of Rational Field
+ Traceback (most recent call last):
+ ...
+ TypeError: Objects of categories must be instances of , but 2/3 isn't
 This example illustrates the optional third argument:
+ This example illustrates the optional third argument::
+
sage: QQ.Hom(ZZ, Sets())
Set of Morphisms from Rational Field to Integer Ring in Category of sets
"""