Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
""" Caching facility for SymPy """ from __future__ import print_function, division
from distutils.version import LooseVersion as V
class _cache(list): """ List of cached functions """
def print_cache(self): """print cache info"""
for item in self: name = item.__name__ myfunc = item while hasattr(myfunc, '__wrapped__'): if hasattr(myfunc, 'cache_info'): info = myfunc.cache_info() break else: myfunc = myfunc.__wrapped__ else: info = None
print(name, info)
def clear_cache(self): """clear cache content""" else: myfunc = myfunc.__wrapped__
# global cache registry: CACHE = _cache() # make clear and print methods available print_cache = CACHE.print_cache clear_cache = CACHE.clear_cache
from sympy.core.compatibility import lru_cache from functools import update_wrapper
try: import fastcache from warnings import warn # the version attribute __version__ is not present for all versions if not hasattr(fastcache, '__version__'): warn("fastcache version >= 0.4.0 required", UserWarning) raise ImportError # ensure minimum required version of fastcache is present if V(fastcache.__version__) < '0.4.0': warn("fastcache version >= 0.4.0 required, detected {}"\ .format(fastcache.__version__), UserWarning) raise ImportError # Do not use fastcache if running under pypy import platform if platform.python_implementation() == 'PyPy': raise ImportError
except ImportError:
def __cacheit(maxsize): """caching decorator.
important: the result of cached function must be *immutable*
Examples ========
>>> from sympy.core.cache import cacheit >>> @cacheit ... def f(a, b): ... return a+b
>>> @cacheit ... def f(a, b): ... return [a, b] # <-- WRONG, returns mutable object
to force cacheit to check returned results mutability and consistency, set environment variable SYMPY_USE_CACHE to 'debug' """ def func_wrapper(func): cfunc = lru_cache(maxsize, typed=True)(func)
# wraps here does not propagate all the necessary info # for py2.7, use update_wrapper below def wrapper(*args, **kwargs): except TypeError: retval = func(*args, **kwargs)
wrapper.cache_info = cfunc.cache_info wrapper.cache_clear = cfunc.cache_clear
# Some versions of update_wrapper erroneously assign the final # function of the wrapper chain to __wrapped__, see # https://bugs.python.org/issue17482 . # To work around this, we need to call update_wrapper first, then # assign to wrapper.__wrapped__. update_wrapper(wrapper, func) wrapper.__wrapped__ = cfunc.__wrapped__
CACHE.append(wrapper) return wrapper
return func_wrapper else:
def __cacheit(maxsize): """caching decorator.
important: the result of cached function must be *immutable*
Examples ========
>>> from sympy.core.cache import cacheit >>> @cacheit ... def f(a, b): ... return a+b
>>> @cacheit ... def f(a, b): ... return [a, b] # <-- WRONG, returns mutable object
to force cacheit to check returned results mutability and consistency, set environment variable SYMPY_USE_CACHE to 'debug' """ def func_wrapper(func):
cfunc = fastcache.clru_cache(maxsize, typed=True, unhashable='ignore')(func) CACHE.append(cfunc) return cfunc
return func_wrapper ########################################
def __cacheit_nocache(func): return func
def __cacheit_debug(maxsize): """cacheit + code to check cache consistency""" def func_wrapper(func): from .decorators import wraps
cfunc = __cacheit(maxsize)(func)
@wraps(func) def wrapper(*args, **kw_args): # always call function itself and compare it with cached version r1 = func(*args, **kw_args) r2 = cfunc(*args, **kw_args)
# try to see if the result is immutable # # this works because: # # hash([1,2,3]) -> raise TypeError # hash({'a':1, 'b':2}) -> raise TypeError # hash((1,[2,3])) -> raise TypeError # # hash((1,2,3)) -> just computes the hash hash(r1), hash(r2)
# also see if returned values are the same if r1 != r2: raise RuntimeError("Returned values are not the same") return r1 return wrapper return func_wrapper
def _getenv(key, default=None): from os import getenv return getenv(key, default)
# SYMPY_USE_CACHE=yes/no/debug USE_CACHE = _getenv('SYMPY_USE_CACHE', 'yes').lower() # SYMPY_CACHE_SIZE=some_integer/None # special cases : # SYMPY_CACHE_SIZE=0 -> No caching # SYMPY_CACHE_SIZE=None -> Unbounded caching scs = _getenv('SYMPY_CACHE_SIZE', '1000') if scs.lower() == 'none': SYMPY_CACHE_SIZE = None else: try: SYMPY_CACHE_SIZE = int(scs) except ValueError: raise RuntimeError( 'SYMPY_CACHE_SIZE must be a valid integer or None. ' + \ 'Got: %s' % SYMPY_CACHE_SIZE)
if USE_CACHE == 'no': cacheit = __cacheit_nocache elif USE_CACHE == 'yes': cacheit = __cacheit(SYMPY_CACHE_SIZE) elif USE_CACHE == 'debug': cacheit = __cacheit_debug(SYMPY_CACHE_SIZE) # a lot slower else: raise RuntimeError( 'unrecognized value for SYMPY_USE_CACHE: %s' % USE_CACHE) |