PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/wxgeometrie/sympy/core/cache.py

https://github.com/Grahack/geophar
Python | 310 lines | 272 code | 14 blank | 24 comment | 9 complexity | 967b7f4d9f690809eb5b1eed2a675a59 MD5 | raw file
  1. """ Caching facility for SymPy """
  2. # TODO: refactor CACHE & friends into class?
  3. # global cache registry:
  4. CACHE = [] # [] of
  5. # (item, {} or tuple of {})
  6. from sympy.core.logic import fuzzy_bool
  7. from sympy.core.decorators import wraps
  8. def print_cache():
  9. """print cache content"""
  10. for item, cache in CACHE:
  11. item = str(item)
  12. head = '='*len(item)
  13. print head
  14. print item
  15. print head
  16. if not isinstance(cache, tuple):
  17. cache = (cache,)
  18. shown = False
  19. else:
  20. shown = True
  21. for i, kv in enumerate(cache):
  22. if shown:
  23. print '\n*** %i ***\n' % i
  24. for k, v in kv.iteritems():
  25. print ' %s :\t%s' % (k, v)
  26. def clear_cache():
  27. """clear cache content"""
  28. for item, cache in CACHE:
  29. if not isinstance(cache, tuple):
  30. cache = (cache,)
  31. for kv in cache:
  32. kv.clear()
  33. ########################################
  34. def __cacheit_nocache(func):
  35. return func
  36. def __cacheit(func):
  37. """caching decorator.
  38. important: the result of cached function must be *immutable*
  39. **Example**
  40. >>> from sympy.core.cache import cacheit
  41. >>> @cacheit
  42. ... def f(a,b):
  43. ... return a+b
  44. >>> @cacheit
  45. ... def f(a,b):
  46. ... return [a,b] # <-- WRONG, returns mutable object
  47. to force cacheit to check returned results mutability and consistency,
  48. set environment variable SYMPY_USE_CACHE to 'debug'
  49. """
  50. func._cache_it_cache = func_cache_it_cache = {}
  51. CACHE.append((func, func_cache_it_cache))
  52. @wraps(func)
  53. def wrapper(*args, **kw_args):
  54. """
  55. Assemble the args and kw_args to compute the hash.
  56. It is important that kw_args be standardized since if they
  57. have the same meaning but in different forms (e.g. one
  58. kw_arg having a value of 1 for an object and another object
  59. with identical args but a kw_arg of True) then two different
  60. hashes will be computed and the two objects will not be identical.
  61. """
  62. if kw_args:
  63. keys = kw_args.keys()
  64. # make keywords all the same
  65. for k in keys:
  66. kw_args[k] = fuzzy_bool(kw_args[k])
  67. keys.sort()
  68. items = [(k+'=', kw_args[k]) for k in keys]
  69. k = args + tuple(items)
  70. else:
  71. k = args
  72. k = k + tuple(map(lambda x: type(x), k))
  73. try:
  74. return func_cache_it_cache[k]
  75. except KeyError:
  76. pass
  77. func_cache_it_cache[k] = r = func(*args, **kw_args)
  78. return r
  79. return wrapper
  80. def __cacheit_debug(func):
  81. """cacheit + code to check cache consistency"""
  82. cfunc = __cacheit(func)
  83. @wraps(func)
  84. def wrapper(*args, **kw_args):
  85. # always call function itself and compare it with cached version
  86. r1 = func (*args, **kw_args)
  87. r2 = cfunc(*args, **kw_args)
  88. # try to see if the result is immutable
  89. #
  90. # this works because:
  91. #
  92. # hash([1,2,3]) -> raise TypeError
  93. # hash({'a':1, 'b':2}) -> raise TypeError
  94. # hash((1,[2,3])) -> raise TypeError
  95. #
  96. # hash((1,2,3)) -> just computes the hash
  97. hash(r1), hash(r2)
  98. # also see if returned values are the same
  99. assert r1 == r2
  100. return r1
  101. return wrapper
  102. class MemoizerArg:
  103. """ See Memoizer.
  104. """
  105. def __init__(self, allowed_types, converter = None, name = None):
  106. self._allowed_types = allowed_types
  107. self.converter = converter
  108. self.name = name
  109. def fix_allowed_types(self, have_been_here={}):
  110. from basic import C
  111. i = id(self)
  112. if have_been_here.get(i): return
  113. allowed_types = self._allowed_types
  114. if isinstance(allowed_types, str):
  115. self.allowed_types = getattr(C, allowed_types)
  116. elif isinstance(allowed_types, (tuple, list)):
  117. new_allowed_types = []
  118. for t in allowed_types:
  119. if isinstance(t, str):
  120. t = getattr(C, t)
  121. new_allowed_types.append(t)
  122. self.allowed_types = tuple(new_allowed_types)
  123. else:
  124. self.allowed_types = allowed_types
  125. have_been_here[i] = True
  126. return
  127. def process(self, obj, func, index = None):
  128. if isinstance(obj, self.allowed_types):
  129. if self.converter is not None:
  130. obj = self.converter(obj)
  131. return obj
  132. func_src = '%s:%s:function %s' % (func.func_code.co_filename, func.func_code.co_firstlineno, func.func_name)
  133. if index is None:
  134. raise ValueError('%s return value must be of type %r but got %r' % (func_src, self.allowed_types, obj))
  135. if isinstance(index, (int,long)):
  136. raise ValueError('%s %s-th argument must be of type %r but got %r' % (func_src, index, self.allowed_types, obj))
  137. if isinstance(index, str):
  138. raise ValueError('%s %r keyword argument must be of type %r but got %r' % (func_src, index, self.allowed_types, obj))
  139. raise NotImplementedError(repr((index,type(index))))
  140. class Memoizer:
  141. """ Memoizer function decorator generator.
  142. Features:
  143. - checks that function arguments have allowed types
  144. - optionally apply converters to arguments
  145. - cache the results of function calls
  146. - optionally apply converter to function values
  147. Usage:
  148. @Memoizer(<allowed types for argument 0>,
  149. MemoizerArg(<allowed types for argument 1>),
  150. MemoizerArg(<allowed types for argument 2>, <convert argument before function call>),
  151. MemoizerArg(<allowed types for argument 3>, <convert argument before function call>, name=<kw argument name>),
  152. ...
  153. return_value_converter = <None or converter function, usually makes a copy>
  154. )
  155. def function(<arguments>, <kw_arguments>):
  156. ...
  157. Details:
  158. - if allowed type is string object then there `C` must have attribute
  159. with the string name that is used as the allowed type --- this is needed
  160. for applying Memoizer decorator to Basic methods when Basic definition
  161. is not defined.
  162. Restrictions:
  163. - arguments must be immutable
  164. - when function values are mutable then one must use return_value_converter to
  165. deep copy the returned values
  166. Ref: http://en.wikipedia.org/wiki/Memoization
  167. """
  168. def __init__(self, *arg_templates, **kw_arg_templates):
  169. new_arg_templates = []
  170. for t in arg_templates:
  171. if not isinstance(t, MemoizerArg):
  172. t = MemoizerArg(t)
  173. new_arg_templates.append(t)
  174. self.arg_templates = tuple(new_arg_templates)
  175. return_value_converter = kw_arg_templates.pop('return_value_converter', None)
  176. self.kw_arg_templates = kw_arg_templates.copy()
  177. for template in self.arg_templates:
  178. if template.name is not None:
  179. self.kw_arg_templates[template.name] = template
  180. if return_value_converter is None:
  181. self.return_value_converter = lambda obj: obj
  182. else:
  183. self.return_value_converter = return_value_converter
  184. def fix_allowed_types(self, have_been_here={}):
  185. i = id(self)
  186. if have_been_here.get(i): return
  187. for t in self.arg_templates:
  188. t.fix_allowed_types()
  189. for k,t in self.kw_arg_templates.items():
  190. t.fix_allowed_types()
  191. have_been_here[i] = True
  192. def __call__(self, func):
  193. cache = {}
  194. value_cache = {}
  195. CACHE.append((func, (cache, value_cache)))
  196. @wraps(func)
  197. def wrapper(*args, **kw_args):
  198. kw_items = tuple(kw_args.items())
  199. try:
  200. return self.return_value_converter(cache[args,kw_items])
  201. except KeyError:
  202. pass
  203. self.fix_allowed_types()
  204. new_args = tuple([template.process(a,func,i) for (a, template, i) in zip(args, self.arg_templates, range(len(args)))])
  205. assert len(args)==len(new_args)
  206. new_kw_args = {}
  207. for k, v in kw_items:
  208. template = self.kw_arg_templates[k]
  209. v = template.process(v, func, k)
  210. new_kw_args[k] = v
  211. new_kw_items = tuple(new_kw_args.items())
  212. try:
  213. return self.return_value_converter(cache[new_args, new_kw_items])
  214. except KeyError:
  215. r = func(*new_args, **new_kw_args)
  216. try:
  217. try:
  218. r = value_cache[r]
  219. except KeyError:
  220. value_cache[r] = r
  221. except TypeError:
  222. pass
  223. cache[new_args, new_kw_items] = cache[args, kw_items] = r
  224. return self.return_value_converter(r)
  225. return wrapper
  226. class Memoizer_nocache(Memoizer):
  227. def __call__(self, func):
  228. # XXX I would be happy just to return func, but we need to provide
  229. # argument convertion, and it is really needed for e.g. Float("0.5")
  230. @wraps(func)
  231. def wrapper(*args, **kw_args):
  232. kw_items = tuple(kw_args.items())
  233. self.fix_allowed_types()
  234. new_args = tuple([template.process(a,func,i) for (a, template, i) in zip(args, self.arg_templates, range(len(args)))])
  235. assert len(args)==len(new_args)
  236. new_kw_args = {}
  237. for k, v in kw_items:
  238. template = self.kw_arg_templates[k]
  239. v = template.process(v, func, k)
  240. new_kw_args[k] = v
  241. r = func(*new_args, **new_kw_args)
  242. return self.return_value_converter(r)
  243. return wrapper
  244. # SYMPY_USE_CACHE=yes/no/debug
  245. def __usecache():
  246. import os
  247. return os.getenv('SYMPY_USE_CACHE', 'yes').lower()
  248. usecache = __usecache()
  249. if usecache=='no':
  250. Memoizer = Memoizer_nocache
  251. cacheit = __cacheit_nocache
  252. elif usecache=='yes':
  253. cacheit = __cacheit
  254. elif usecache=='debug':
  255. cacheit = __cacheit_debug # a lot slower
  256. else:
  257. raise RuntimeError('unknown argument in SYMPY_USE_CACHE: %s' % usecache)