Commits (5)
......@@ -3,14 +3,15 @@ Cached Functions and Methods
AUTHORS:
- William Stein (inspired by conversation with Justin Walker).
- Mike Hansen (added doctests and made it work with class methods).
- Willem Jan Palenstijn (add CachedMethodCaller for binding cached
methods to instances).
- Tom Boothby (added DiskCachedFunction).
- Simon King (improved performance, more doctests, cython version,
CachedMethodCallerNoArgs, weak cached function, cached special methods).
- Julian Rueth (2014-03-19): added ``key`` parameter
- William Stein: initial version, (inspired by conversation with Justin Walker)
- Mike Hansen: added doctests and made it work with class methods.
- Willem Jan Palenstijn: add CachedMethodCaller for binding cached methods to
instances.
- Tom Boothby: added DiskCachedFunction.
- Simon King: improved performance, more doctests, cython version,
CachedMethodCallerNoArgs, weak cached function, cached special methods.
- Julian Rueth (2014-03-19, 2014-05-09): added ``key`` parameter, allow caching
for unhashable elements
EXAMPLES:
......@@ -465,6 +466,44 @@ def _cached_function_unpickle(module,name):
"""
return getattr(__import__(module, fromlist=['']),name)
def _cache_key(o):
r"""
Helper function to return a hashable key for ``o`` which can be used for
caching.
This function is intended for objects which are not hashable such as
`p`-adic numbers. The difference from calling an object's ``_cache_key``
method directly, is that it also works for tuples and unpacks them
recursively.
EXAMPLES::
sage: from sage.misc.cachefunc import _cache_key
sage: K.<u> = Qq(9)
sage: a = K(1); a
1 + O(3^20)
sage: _cache_key(a)
(((1,),), 0, 20)
This function works if ``o`` is a tuple. In this case it unpacks its
entries recursively::
sage: o = (1, 2, (3, a))
sage: _cache_key(o)
(1, 2, (3, (((1,),), 0, 20)))
.. SEEALSO::
:meth:`sage.structure.sage_object.SageObject._cache_key`
"""
if isinstance(o, sage.structure.sage_object.SageObject):
o = o._cache_key()
if isinstance(o,tuple):
return tuple(_cache_key(item) for item in o)
else:
return o
cdef class CachedFunction(object):
"""
Create a cached version of a function, which only recomputes
......@@ -487,7 +526,8 @@ cdef class CachedFunction(object):
def f(...):
....
The inputs to the function must be hashable.
The inputs to the function must be hashable or they must define
:meth:`sage.structure.sage_object.SageObject._cache_key`.
EXAMPLES::
......@@ -543,7 +583,8 @@ cdef class CachedFunction(object):
def f(...):
....
The inputs to the function must be hashable.
The inputs to the function must be hashable or they must define
:meth:`sage.structure.sage_object.SageObject._cache_key`.
TESTS::
......@@ -775,6 +816,25 @@ cdef class CachedFunction(object):
sage: a is number_of_partitions(10^5)
True
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: @cached_function
....: def f(x): return x
sage: K.<u> = Qq(4)
sage: x = K(1,1); x
1 + O(2)
sage: y = K(1,2); y
1 + O(2^2)
sage: x==y
True
sage: f(x)
1 + O(2)
sage: f(y)
1 + O(2^2)
sage: f.cache
{(((((1,),), 0, 1),), ()): 1 + O(2), (((((1,),), 0, 2),), ()): 1 + O(2^2)}
"""
# We shortcut a common case of no arguments
if args or kwds:
......@@ -790,7 +850,11 @@ cdef class CachedFunction(object):
k = self._default_key = self._fix_to_pos()
try:
return (<dict>self.cache)[k]
try:
return (<dict>self.cache)[k]
except TypeError: # k is not hashable
k = _cache_key(k)
return (<dict>self.cache)[k]
except KeyError:
w = self.f(*args, **kwds)
self.cache[k] = w
......@@ -828,10 +892,32 @@ cdef class CachedFunction(object):
6
sage: a.f.is_in_cache(3,y=0)
True
TESTS:
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: @cached_function
....: def f(x): return x
sage: K.<u> = Qq(4)
sage: x = K(1,1); x
1 + O(2)
sage: f.is_in_cache(x)
False
sage: f(x)
1 + O(2)
sage: f.is_in_cache(x)
True
"""
if self._argument_fixer is None:
self.argfix_init()
return self._fix_to_pos(*args, **kwds) in (<dict>self.cache)
k = self._fix_to_pos(*args, **kwds)
try:
return k in (<dict>self.cache)
except TypeError: # k is not hashable
return _cache_key(k) in (<dict>self.cache)
def set_cache(self, value, *args, **kwds):
"""
......@@ -851,6 +937,20 @@ cdef class CachedFunction(object):
sage: g(5)
17
TESTS:
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: @cached_function
....: def f(x): return x
sage: K.<u> = Qq(4)
sage: x = K(1,1); x
1 + O(2)
sage: f.set_cache(x,x)
sage: f.is_in_cache(x)
True
DEVELOPER NOTE:
Is there a way to use the following intuitive syntax?
......@@ -863,7 +963,11 @@ cdef class CachedFunction(object):
"""
if self._argument_fixer is None:
self.argfix_init()
(<dict>self.cache)[self._fix_to_pos(*args, **kwds)] = value
k = self._fix_to_pos(*args, **kwds)
try:
(<dict>self.cache)[k] = value
except TypeError: # k is not hashable
(<dict>self.cache)[_cache_key(k)] = value
def get_key(self, *args, **kwds):
"""
......@@ -997,7 +1101,8 @@ cdef class WeakCachedFunction(CachedFunction):
"""
def __init__(self, f, classmethod=False, name=None, key=None):
"""
The inputs to the function must be hashable.
The inputs to the function must be hashable or they must define
:meth:`sage.structure.sage_object.SageObject._cache_key`.
The outputs to the function must be weakly referenceable.
TESTS::
......@@ -1057,6 +1162,27 @@ cdef class WeakCachedFunction(CachedFunction):
sage: a = f()
doing a computation
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: from sage.misc.cachefunc import weak_cached_function
sage: @weak_cached_function
....: def f(x): return x
sage: K.<u> = Qq(4)
sage: R.<t> = K[]
sage: x = t + K(1,1); x
(1 + O(2^20))*t + 1 + O(2)
sage: y = t + K(1,2); y
(1 + O(2^20))*t + 1 + O(2^2)
sage: x==y
True
sage: f(x)
(1 + O(2^20))*t + 1 + O(2)
sage: f(y)
(1 + O(2^20))*t + 1 + O(2^2)
sage: list(f.cache.keys())
[((((((1,),), 0, 2), (((1,),), 0, 20)),), ()), ((((((1,),), 0, 1), (((1,),), 0, 20)),), ())]
"""
# We shortcut a common case of no arguments
if args or kwds:
......@@ -1072,7 +1198,11 @@ cdef class WeakCachedFunction(CachedFunction):
k = self._default_key = self._fix_to_pos()
try:
return self.cache[k]
try:
return self.cache[k]
except TypeError: # k is not hashable
k = _cache_key(k)
return self.cache[k]
except KeyError:
w = self.f(*args, **kwds)
self.cache[k] = w
......@@ -1108,10 +1238,31 @@ cdef class WeakCachedFunction(CachedFunction):
sage: f.is_in_cache(5)
False
TESTS:
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: from sage.misc.cachefunc import weak_cached_function
sage: @weak_cached_function
....: def f(x): return x
sage: K.<u> = Qq(4)
sage: R.<t> = K[]
sage: f.is_in_cache(t)
False
sage: f(t)
(1 + O(2^20))*t
sage: f.is_in_cache(t)
True
"""
if self._argument_fixer is None:
self.argfix_init()
return self._fix_to_pos(*args, **kwds) in self.cache
k = self._fix_to_pos(*args, **kwds)
try:
return k in self.cache
except TypeError: # k is not hashable
return _cache_key(k) in self.cache
def set_cache(self, value, *args, **kwds):
"""
......@@ -1133,11 +1284,28 @@ cdef class WeakCachedFunction(CachedFunction):
sage: f(5)
Integer Ring
TESTS:
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: from sage.misc.cachefunc import weak_cached_function
sage: @weak_cached_function
....: def f(x): return x
sage: K.<u> = Qq(4)
sage: R.<t> = K[]
sage: f.set_cache(t,t)
sage: f.is_in_cache(t)
True
"""
if self._argument_fixer is None:
self.argfix_init()
self.cache[self._fix_to_pos(*args, **kwds)] = value
k = self._fix_to_pos(*args, **kwds)
try:
self.cache[k] = value
except TypeError: # k is not hashable
self.cache[_cache_key(k)] = value
weak_cached_function = decorator_keywords(WeakCachedFunction)
......@@ -1545,6 +1713,28 @@ cdef class CachedMethodCaller(CachedFunction):
sage: b = Foo(3)
sage: a.f(b.f)
2
Check that :trac:`16316` has been fixed, i.e., caching works for
objects which are not hashable::
sage: K.<u> = Qq(4)
sage: class A(object):
....: @cached_method
....: def f(self, x): return x
sage: a=A()
sage: x = K(1,1); x
1 + O(2)
sage: y = K(1,2); y
1 + O(2^2)
sage: x==y
True
sage: a.f(x)
1 + O(2)
sage: a.f(y)
1 + O(2^2)
sage: a.f.cache
{(((((1,),), 0, 1),), ()): 1 + O(2), (((((1,),), 0, 2),), ()): 1 + O(2^2)}
"""
if self._instance is None:
# cached method bound to a class
......@@ -1586,7 +1776,11 @@ cdef class CachedMethodCaller(CachedFunction):
else:
k = self._default_key = self._fix_to_pos()
try:
return cache[k]
try:
return cache[k]
except TypeError: # k is not hashable
k = _cache_key(k)
return cache[k]
except KeyError:
w = self._cachedmethod._instance_call(self._instance, *args, **kwds)
cache[k] = w
......@@ -2051,9 +2245,10 @@ cdef class CachedMethod(object):
.. NOTE::
For proper behavior, the method must be a pure function
(no side effects). Arguments to the method must be hashable
or transformed into something hashable using ``key``.
For proper behavior, the method must be a pure function (no side
effects). Arguments to the method must be hashable or transformed into
something hashable using ``key`` or they must define
:meth:`sage.structure.sage_object.SageObject._cache_key`.
EXAMPLES::
......@@ -2577,15 +2772,17 @@ cdef class CachedInParentMethod(CachedMethod):
This way of caching works only if
- the instances *have* a parent, and
- the instances are hashable (they are part of the cache key).
- the instances are hashable (they are part of the cache key) or they
define :meth:`sage.structure.sage_object.SageObject._cache_key`
NOTE:
For proper behavior, the method must be a pure function (no side
effects). If this decorator is used on a method, it will have
identical output on equal elements. This is since the element is
part of the hash key. Arguments to the method and the instance
it is assigned to must be hashable.
For proper behavior, the method must be a pure function (no side effects).
If this decorator is used on a method, it will have identical output on
equal elements. This is since the element is part of the hash key.
Arguments to the method must be hashable or define
:meth:`sage.structure.sage_object.SageObject._cache_key`. The instance it
is assigned to must be hashable.
Examples can be found at :mod:`~sage.misc.cachefunc`.
......
......@@ -48,7 +48,8 @@ AUTHORS:
- Craig Citro (2008-02-16): speed up __call__ method for
Dirichlet characters, miscellaneous fixes
- Julian Rueth (2014-03-06): use UniqueFactory to cache DirichletGroups
- Julian Rueth (2014-03-06, 2014-04-28): use UniqueFactory to cache
DirichletGroups; fixed Dirichlet groups over rings with unhashable elements
"""
......@@ -408,36 +409,6 @@ class DirichletCharacter(MultiplicativeGroupElement):
x = self.element() + other.element()
return DirichletCharacter(self.parent(), x, check=False)
#values_on_gens = [self.__values_on_gens[i]*other.__values_on_gens[i]
#for i in range(len(self.__values_on_gens))]
# return DirichletCharacter(self.parent(), values_on_gens)
## def x_mul_(self, other):
## """
## Return the product of self and other.
## EXAMPLES:
## sage: G.<a,b> = DirichletGroup(20)
## sage: a
## [-1, 1]
## sage: b
## [1, zeta4]
## sage: a*b
## [-1, zeta4]
## """
## values_on_gens = [self.__values_on_gens[i]*other.__values_on_gens[i]
## for i in range(len(self.__values_on_gens))]
## return DirichletCharacter(self.parent(), values_on_gens)
## P = self.parent()
## dlog = P._zeta_dlog
## pows = P._zeta_powers
## n = len(pows)
## values_on_gens = [None]*len(self.__values_on_gens)
## for i in range(len(self.__values_on_gens)):
## k = (dlog[self.__values_on_gens[i]] + dlog[other.__values_on_gens[i]]) % n
## values_on_gens[i] = pows[k]
## return DirichletCharacter(self.parent(), values_on_gens)
def __copy__(self):
"""
Return a (shallow) copy of this Dirichlet character.
......@@ -1616,14 +1587,7 @@ class DirichletCharacter(MultiplicativeGroupElement):
def element(self):
r"""
Return the underlying `\ZZ/n\ZZ`-module
vector of exponents.
.. warning::
Please do not change the entries of the returned vector;
this vector is mutable *only* because immutable vectors are
implemented yet.
Return the underlying `\ZZ/n\ZZ`-module vector of exponents.
EXAMPLES::
......@@ -1632,6 +1596,15 @@ class DirichletCharacter(MultiplicativeGroupElement):
(2, 0)
sage: b.element()
(0, 1)
TESTS:
Verify that :trac:`16258` has been resolved, i.e., Dirichlet groups
work over rings whose elements are unhashable::
sage: K.<a> = Qq(9)
sage: G = DirichletGroup(2,base_ring=K,zeta=K(-1),zeta_order=2)
"""
try:
return self.__element
......@@ -1643,14 +1616,23 @@ class DirichletCharacter(MultiplicativeGroupElement):
R = C._real_field()
zeta = P._zeta
zeta_argument = zeta.argument()
v = M([int(round(x.argument()/zeta_argument))
for x in self.values_on_gens()])
ret = [int(round(x.argument()/zeta_argument))
for x in self.values_on_gens()]
else:
dlog = P._zeta_dlog
v = M([dlog[x] for x in self.values_on_gens()])
self.__element = v
return v
if dlog:
ret = [dlog[x] for x in self.values_on_gens()]
else:
# if the elements of the base ring are unhashable, dlog is None and we
# have to step through the powers of zeta manually
powers = P._zeta_powers
ret = [powers.index(x) for x in self.values_on_gens()]
ret = M(ret)
ret.set_immutable()
self.__element = ret
return ret
class DirichletGroupFactory(UniqueFactory):
r"""
......@@ -1907,28 +1889,43 @@ class DirichletGroup_class(parent_gens.ParentWithMultiplicativeAbelianGens):
sage: G = DirichletGroup(7, base_ring = Integers(9), zeta = Integers(9)(2)) # indirect doctest
sage: G.base() # check that ParentWithBase.__init__ has been called
Ring of integers modulo 9
TESTS:
Verify that :trac:`16258` has been resolved, i.e., Dirichlet groups
work over rings whose elements are unhashable::
sage: K.<a> = Qq(9)
sage: DirichletGroup(2,base_ring=K,zeta=K(-1),zeta_order=2)
Group of Dirichlet characters of modulus 2 over Unramified Extension of 3-adic Field with capped relative precision 20 in a defined by (1 + O(3^20))*x^2 + (2 + O(3^20))*x + (2 + O(3^20))
"""
parent_gens.ParentWithMultiplicativeAbelianGens.__init__(self, zeta.parent())
self._zeta = zeta
self._zeta_order = int(zeta_order)
self._modulus = modulus
self._integers = rings.IntegerModRing(modulus)
# precompute the powers of zeta and store them in _zeta_powers
a = zeta.parent()(1)
v = {a:0}
w = [a]
if is_ComplexField(zeta.parent()):
for i in range(1, self._zeta_order):
a = a * zeta
a._set_multiplicative_order(zeta_order/arith.GCD(zeta_order, i))
v[a] = i
w.append(a)
else:
for i in range(1, self._zeta_order):
a = a * zeta
v[a] = i
w.append(a)
self._zeta_powers = w # gives quickly the ith power of zeta
self._zeta_dlog = v # dictionary that computes log_{zeta}(power of zeta).
self._zeta_powers = w
# from _zeta_powers, create a dictionary zeta^i -> i to quickly compute discrete logarithms
try:
self._zeta_dlog = { v:i for (i,v) in enumerate(self._zeta_powers) }
except TypeError: # this is not possible if the powers of zeta are unhashable (e.g. for p-adics)
self._zeta_dlog = None
self._module = free_module.FreeModule(rings.IntegerModRing(zeta_order),
len(self._integers.unit_gens()))
......
......@@ -163,15 +163,18 @@ NOTES::
AUTHORS:
- David Roe (2008-01-01) initial version
- David Roe (2008-01-01): initial version
- Robert Harron (2011-09) fixes/enhancements
- Robert Harron (2011-09): fixes/enhancements
- Julian Rueth (2014-05-09): enable caching through ``_cache_key``
"""
#*****************************************************************************
# Copyright (C) 2008 David Roe <roed.math@gmail.com>
# William Stein <wstein@gmail.com>
# 2014 Julian Rueth <julian.rueth@fsfe.org>
#
# Distributed under the terms of the GNU General Public License (GPL)
# as published by the Free Software Foundation; either version 2 of
......@@ -469,6 +472,57 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement):
else:
self._set_from_list_both(x, aprec, rprec)
def _cache_key(self):
r"""
Return a hashable key which uniquely identifies this element among all
elements in the parent.
This enables caching for `p`-adic numbers which are not hashable.
.. SEEALSO::
:meth:`sage.structure.sage_object.SageObject._cache_key`
EXAMPLE:
In the following example, ``a`` and ``b`` compare equal. They can not
have a meaningful hash value since then their hash value would have to
be the same::
sage: K.<a> = Qq(9)
sage: b = a + O(3)
sage: a == b
True
sage: hash(a)
Traceback (most recent call last):
...
TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement'
However, they should be cacheable, therefore they define a
``_cache_key`` which is hashable and uniquely identifies them::
sage: a._cache_key()
(((0, 1),), 0, 20)
sage: b._cache_key()
(((0, 1),), 0, 1)
TESTS:
Check that zero values are handled correctly::
sage: K.zero()._cache_key()
0
sage: K(0,1)._cache_key()
(0, 1)
"""
if self._is_exact_zero():
return 0
elif self._is_inexact_zero():
return 0, self.valuation()
else:
return tuple(tuple(c) if isinstance(c,list) else c for c in self.unit_part().list()), self.valuation(), self.precision_relative()
cdef int _set_inexact_zero(self, long absprec) except -1:
"""
Sets ``self`` to be zero with valuation absprec.
......
......@@ -13,8 +13,9 @@ AUTHORS:
- Simon King: Use a faster way of conversion from the base ring.
- Julian Rueth (2012-05-25): Fixed is_squarefree() for imperfect fields.
Fixed division without remainder over QQbar.
- Julian Rueth (2012-05-25,2014-05-09): Fixed is_squarefree() for imperfect
fields, fixed division without remainder over QQbar, added ``_cache_key``
for polynomials with unhashable coefficients
- Simon King (2013-10): Implement copying of :class:`PolynomialBaseringInjection`.
......@@ -829,6 +830,39 @@ cdef class Polynomial(CommutativeAlgebraElement):
def __iter__(self):
return iter(self.list())
def _cache_key(self):
"""
Return a key which uniquely identifies this element among all elements
in the parent.
.. SEEALSO::
:meth:`sage.structure.sage_object.SageObject._cache_key`
EXAMPLES:
This enables caching for polynomials with unhashable coefficients such
as `p`-adics::
sage: K.<u> = Qq(4)
sage: R.<x> = K[]
sage: f = x
sage: hash(f)
Traceback (most recent call last):
...
TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement'
sage: f._cache_key()
(0, 1 + O(2^20))
sage: @cached_function
....: def foo(t): return t
....:
sage: foo(x)
(1 + O(2^20))*x
"""
return tuple(self)
# you may have to replicate this boilerplate code in derived classes if you override
# __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html
# explains how __richcmp__, __hash__, and __cmp__ are tied together.
......
......@@ -37,13 +37,15 @@ easier to use than a factory.
AUTHORS:
- Robert Bradshaw (2008), initial version.
- Simon King (2013), extended documentation.
- Robert Bradshaw (2008): initial version.
- Simon King (2013): extended documentation.
- Julian Rueth (2014-05-09): use ``_cache_key`` if parameters are unhashable
"""
#*****************************************************************************
# Copyright (C) 2008 Robert Bradshaw <robertwb@math.washington.edu>
# 2014 Julian Rueth <julian.rueth@fsfe.org>
#
# Distributed under the terms of the GNU General Public License (GPL)
#
......@@ -383,23 +385,37 @@ cdef class UniqueFactory(SageObject):
sage: test_factory.get_object(3.0, 'a', {}) is test_factory.get_object(3.0, 'b', {})
Making object b
False
TESTS:
Check that :trac:`16317` has been fixed, i.e., caching works for
unhashable objects::
sage: K.<u> = Qq(4)
sage: test_factory.get_object(3.0, (K(1), 'c'), {}) is test_factory.get_object(3.0, (K(1), 'c'), {})
Making object (1 + O(2^20), 'c')
True
"""
cache_key = key
try:
return self._cache[version, key]
try:
return self._cache[version, cache_key]
except TypeError: # key is unhashable
from sage.misc.cachefunc import _cache_key
cache_key = _cache_key(cache_key)
return self._cache[version, cache_key]
except KeyError:
pass
obj = self.create_object(version, key, **extra_args)
self._cache[version, key] = obj
self._cache[version, cache_key] = obj
try:
other_keys = self.other_keys(key, obj)
for key in other_keys:
for key in self.other_keys(key, obj):
try:
obj = self._cache[version, key]
break
except KeyError:
pass
for key in other_keys:
self._cache[version, key] = obj
self._cache[version, key] = obj
except TypeError: # key is unhashable
from sage.misc.cachefunc import _cache_key
self._cache[version, _cache_key(key)] = obj
obj._factory_data = self, version, key, extra_args
if obj.__class__.__reduce__.__objclass__ is object:
# replace the generic object __reduce__ to use this one
......
......@@ -221,6 +221,75 @@ cdef class SageObject:
def __hash__(self):
return hash(self.__repr__())
def _cache_key(self):
r"""
Return a hashable key which uniquely identifies this objects for
caching.
For most objects this will just be the object itself. However, some
immutable objects (such as `p`-adic numbers) can not implement a
reasonable hash function because their ``==`` operator has been
modified to return ``True`` for objects which might behave differently
in some computations::
sage: K.<a> = Qq(9)
sage: b = a + O(3)
sage: c = a + 3
sage: b
a + O(3)
sage: c
a + 3 + O(3^20)
sage: b == c
True
sage: b == a
True
sage: c == a
False
If such objects defined a non-trivial hash function, this would break
caching in many places. However, such objects should still be
cacheable. This can be achieved by defining an appropriate
``_cache_key``::
sage: hash(b)
Traceback (most recent call last):
...
TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement'
sage: @cached_method
....: def f(x): return x==a
sage: f(b)
True
sage: f(c) # if b and c were hashable, this would return True
False
sage: b._cache_key()
(((0, 1),), 0, 1)
sage: c._cache_key()
(((0, 1), (1,)), 0, 20)
Note that such ``_cache_key`` often does not uniquely identify an
object in a strict sense::
sage: S.<a> = Qq(4)
sage: d = a + O(2)
sage: b._cache_key() == d._cache_key()
True
However, this kind of behaviour is common for many elements with
different parents. Special care has to be taken when mixing such
elements in caches::
sage: A = matrix([[1]],base_ring=GF(2))
sage: A.set_immutable()
sage: B = matrix([[1]],base_ring=GF(3))
sage: B.set_immutable()
sage: A == B
True
sage: hash(A) == hash(B)
True
"""
return self
#############################################################################
# DATABASE Related code
......