Skip to content
This repository was archived by the owner on Jan 30, 2023. It is now read-only.

Commit 7a0f094

Browse files
committed
Introduced _cache_key for sage objects
No meaningful hash function can be defined for elements which overwrite the operator == like the p-adics do since the contract on __hash__ would be broken: elements which compare equal would have different hash values. This would break caches and lead to subtle bugs. Still such elements should be cacheable for performance. As a workaround, such non-hashable elements can define a _cache_key which uniquely identifies them, and so fixes the "broken" == operator.
1 parent 2b1d88b commit 7a0f094

File tree

2 files changed

+125
-2
lines changed

2 files changed

+125
-2
lines changed

src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx

+56-2
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,18 @@ NOTES::
163163
164164
AUTHORS:
165165
166-
- David Roe (2008-01-01) initial version
166+
- David Roe (2008-01-01): initial version
167167
168-
- Robert Harron (2011-09) fixes/enhancements
168+
- Robert Harron (2011-09): fixes/enhancements
169+
170+
- Julian Rueth (2014-05-09): enable caching through ``_cache_key``
169171
170172
"""
171173

172174
#*****************************************************************************
173175
# Copyright (C) 2008 David Roe <[email protected]>
174176
# William Stein <[email protected]>
177+
# 2014 Julian Rueth <[email protected]>
175178
#
176179
# Distributed under the terms of the GNU General Public License (GPL)
177180
# as published by the Free Software Foundation; either version 2 of
@@ -469,6 +472,57 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement):
469472
else:
470473
self._set_from_list_both(x, aprec, rprec)
471474

475+
def _cache_key(self):
476+
r"""
477+
Return a hashable key which uniquely identifies this element among all
478+
elements in the parent.
479+
480+
This enables caching for `p`-adic numbers which are not hashable.
481+
482+
.. SEEALSO::
483+
484+
:meth:`sage.structure.sage_object.SageObject._cache_key`
485+
486+
EXAMPLE:
487+
488+
In the following example, ``a`` and ``b`` compare equal. They can not
489+
have a meaningful hash value since then their hash value would have to
490+
be the same::
491+
492+
sage: K.<a> = Qq(9)
493+
sage: b = a + O(3)
494+
sage: a == b
495+
True
496+
sage: hash(a)
497+
Traceback (most recent call last):
498+
...
499+
TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement'
500+
501+
However, they should be cacheable, therefore they define a
502+
``_cache_key`` which is hashable and uniquely identifies them::
503+
504+
sage: a._cache_key()
505+
(((0, 1),), 0, 20)
506+
sage: b._cache_key()
507+
(((0, 1),), 0, 1)
508+
509+
TESTS:
510+
511+
Check that zero values are handled correctly::
512+
513+
sage: K.zero()._cache_key()
514+
0
515+
sage: K(0,1)._cache_key()
516+
(0, 1)
517+
518+
"""
519+
if self._is_exact_zero():
520+
return 0
521+
elif self._is_inexact_zero():
522+
return 0, self.valuation()
523+
else:
524+
return tuple(tuple(c) if isinstance(c,list) else c for c in self.unit_part().list()), self.valuation(), self.precision_relative()
525+
472526
cdef int _set_inexact_zero(self, long absprec) except -1:
473527
"""
474528
Sets ``self`` to be zero with valuation absprec.

src/sage/structure/sage_object.pyx

+69
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,75 @@ cdef class SageObject:
221221
def __hash__(self):
222222
return hash(self.__repr__())
223223

224+
def _cache_key(self):
225+
r"""
226+
Return a hashable key which uniquely identifies this objects for
227+
caching.
228+
229+
For most objects this will just be the object itself. However, some
230+
immutable objects (such as `p`-adic numbers) can not implement a
231+
reasonable hash function because their ``==`` operator has been
232+
modified to return ``True`` for objects which might behave differently
233+
in some computations::
234+
235+
sage: K.<a> = Qq(9)
236+
sage: b = a + O(3)
237+
sage: c = a + 3
238+
sage: b
239+
a + O(3)
240+
sage: c
241+
a + 3 + O(3^20)
242+
sage: b == c
243+
True
244+
sage: b == a
245+
True
246+
sage: c == a
247+
False
248+
249+
If such objects defined a non-trivial hash function, this would break
250+
caching in many places. However, such objects should still be
251+
cacheable. This can be achieved by defining an appropriate
252+
``_cache_key``::
253+
254+
sage: hash(b)
255+
Traceback (most recent call last):
256+
...
257+
TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement'
258+
sage: @cached_method
259+
....: def f(x): return x==a
260+
sage: f(b)
261+
True
262+
sage: f(c) # if b and c were hashable, this would return True
263+
False
264+
265+
sage: b._cache_key()
266+
(((0, 1),), 0, 1)
267+
sage: c._cache_key()
268+
(((0, 1), (1,)), 0, 20)
269+
270+
Note that such ``_cache_key`` often does not uniquely identify an
271+
object in a strict sense::
272+
273+
sage: S.<a> = Qq(4)
274+
sage: d = a + O(2)
275+
sage: b._cache_key() == d._cache_key()
276+
True
277+
278+
However, this kind of behaviour is common for many elements with
279+
different parents. Special care has to be taken when mixing such
280+
elements in caches::
281+
282+
sage: A = matrix([[1]],base_ring=GF(2))
283+
sage: A.set_immutable()
284+
sage: B = matrix([[1]],base_ring=GF(3))
285+
sage: B.set_immutable()
286+
sage: A == B
287+
True
288+
sage: hash(A) == hash(B)
289+
True
290+
291+
"""
292+
return self
224293

225294
#############################################################################
226295
# DATABASE Related code

0 commit comments

Comments
 (0)