[PATCH 0 of 5] A bit of caching cleanup

1 view
Skip to first unread message

Kirill Smelkov

unread,
Feb 10, 2008, 3:36:41 PM2/10/08
to sympy-...@googlegroups.com
Previously we had cache_it_fast for mutable objects and cache_it_immutable for
immutable ones.

My point is that we always should work with immutable objects and avoid extra
overhead (deep-copy) related to keeping cache safe for mutable things.

This simplifies things a bit for users, so related to #429

Kirill Smelkov

unread,
Feb 10, 2008, 3:36:43 PM2/10/08
to sympy-...@googlegroups.com
# HG changeset patch
# User Kirill Smelkov <ki...@landau.phys.spbu.ru>
# Date 1202674934 -10800
# Node ID 2b33a742b1cead7b36506727e2cad2e37831f5fb
# Parent 084e4f9ed6a9a8142663baf5077863de07ba13d5
[2/5] cache cleanup, hadle only immutable objects

Now we've eliminated all usage of cache_it, let's kill to what it pointed --
cache_it_fast.

Plus kill mycopy (deep-coping)

Correctnes of the whole thing was verified with

SYMPY_USE_CACHE=debug py.test sympy/core/

and

SYMPY_USE_CACHE=debug py.test sympy/


but in the second check cache_it_debug was modified to only check that the
result is immutable. Otherwise we run out of 1G of memory.

diff --git a/sympy/core/cache.py b/sympy/core/cache.py
--- a/sympy/core/cache.py
+++ b/sympy/core/cache.py
@@ -1,15 +1,4 @@
""" Caching facility for SymPy """
-
-def mycopy(obj, level=0):
- if isinstance(obj, (list, tuple)):
- return obj.__class__(map(mycopy, obj))
- elif isinstance(obj, dict):
- d = obj.__class__()
- for k,v in obj.items():
- d[mycopy(k)] = mycopy(v)
- return d
- return obj
-

# TODO: refactor CACHE & friends into class?

@@ -58,28 +47,7 @@ def cache_it_nocache(func):
def cache_it_nocache(func):
return func

-def cache_it_fast(func):
- func._cache_it_cache = func_cache_it_cache = {}
- CACHE.append((func, func_cache_it_cache))

- def wrapper(*args, **kw_args):
- if kw_args:
- keys = kw_args.keys()
- keys.sort()
- items = [(k+'=',kw_args[k]) for k in keys]
- k = args + tuple(items)
- else:
- k = args
- cache_flag = False
- try:
- r = func_cache_it_cache[k]
- except KeyError:
- r = func(*args, **kw_args)
- cache_flag = True
- if cache_flag:
- func_cache_it_cache[k] = r
- return mycopy(r)
- return wrapper

def cache_it_immutable(func):
func._cache_it_cache = func_cache_it_cache = {}
@@ -104,14 +72,20 @@ def cache_it_immutable(func):

def cache_it_debug(func):
"""cache_it_fast + code to check cache consitency"""
- cfunc = cache_it_fast(func)
+ cfunc = __cache_it_immutable(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 ummutable
+ # this is not 100% check, but at least we catch lists usage
+ hash(r1), hash(r2)
+
+ # also see if returned values are the same
assert r1 == r2
+
return r1

return wrapper
@@ -310,15 +284,16 @@ usecache = os.getenv('SYMPY_USE_CACHE',
usecache = os.getenv('SYMPY_USE_CACHE', 'yes').lower()

if usecache=='no':
- cache_it_fast = cache_it_nocache
cache_it_immutable = cache_it_nocache
cache_it_debug = cache_it_nocache
cache_it_nondummy = cache_it_nocache
Memoizer = Memoizer_nocache
cache_it = cache_it_nocache
elif usecache=='yes':
- cache_it = cache_it_fast
+ cache_it = cache_it_immutable
elif usecache=='debug':
cache_it = cache_it_debug # twice slower
+ __cache_it_immutable = cache_it_immutable
+ cache_it_immutable = cache_it_debug
else:
raise RuntimeError('unknown argument in SYMPY_USE_CACHE: %s' % usecache)

Kirill Smelkov

unread,
Feb 10, 2008, 3:36:42 PM2/10/08
to sympy-...@googlegroups.com
# HG changeset patch
# User Kirill Smelkov <ki...@landau.phys.spbu.ru>
# Date 1202674932 -10800
# Node ID 084e4f9ed6a9a8142663baf5077863de07ba13d5
# Parent eed943040df19deefa54c7d2f012f424d52e1783
[1/5] cache cleanup, hadle only immutable objects

don't use cache_it, always use cache_it_immutable

cache_it was created to cache mutable results, but there is no reason for it.

first, to ensure cache itself is kept unmodified from outside, each time return
value is deep-copied and this adds overhead.

second, we have no reason to cache mutable instances -- what we almost always
have is immutable, so why have additional unneccesary overhead (deep copy)?

Very seldom cache_it user which were returning mutable things (like
.as_coeff_factors) were rewrittend to return immutable objects. The performance
may be affected only positively from this since we avoid deep-copy, and
previously lists were used only to do l.insert(0, smth), and the same effect
can be achieved with tuple+tuple.

diff --git a/sympy/core/add.py b/sympy/core/add.py
--- a/sympy/core/add.py
+++ b/sympy/core/add.py
@@ -2,7 +2,7 @@ from basic import Basic, S, C
from basic import Basic, S, C
from operations import AssocOp
from methods import RelMeths, ArithMeths
-from cache import cache_it, cache_it_immutable
+from cache import cache_it_immutable

from symbol import Symbol, Wild, Temporary
# from numbers import Number /cyclic/
@@ -111,7 +111,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
return '(%s)' % r
return r

- @cache_it
+ @cache_it_immutable
def as_coeff_factors(self, x=None):
if x is not None:
l1 = []
@@ -121,11 +121,11 @@ class Add(AssocOp, RelMeths, ArithMeths)
l2.append(f)
else:
l1.append(f)
- return Add(*l1), l2
+ return Add(*l1), tuple(l2)
coeff = self.args[0]
if isinstance(coeff, Number):
- return coeff, list(self.args[1:])
- return S.Zero, list(self.args[:])
+ return coeff, self.args[1:]
+ return S.Zero, self.args

def _eval_derivative(self, s):
return Add(*[f.diff(s) for f in self.args])
@@ -143,7 +143,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
def _combine_inverse(lhs, rhs):
return lhs - rhs

- @cache_it
+ @cache_it_immutable
def as_two_terms(self):
if len(self.args) == 1:
return S.Zero, self
@@ -255,8 +255,8 @@ class Add(AssocOp, RelMeths, ArithMeths)
def as_coeff_terms(self, x=None):
# -2 + 2 * a -> -1, 2-2*a
if isinstance(self.args[0], Number) and self.args[0].is_negative:
- return -S.One,[-self]
- return S.One,[self]
+ return -S.One,(-self,)
+ return S.One,(self,)

def _eval_subs(self, old, new):
if self==old: return new
@@ -278,7 +278,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
def _eval_oseries(self, order):
return Add(*[f.oseries(order) for f in self.args])

- @cache_it
+ @cache_it_immutable
def extract_leading_order(self, *symbols):
lst = []
seq = [(f, C.Order(f, *symbols)) for f in self.args]
@@ -295,7 +295,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
continue
new_lst.append((e,o))
lst = new_lst
- return lst
+ return tuple(lst)

def _eval_as_leading_term(self, x):
coeff, factors = self.as_coeff_factors(x)
diff --git a/sympy/core/basic.py b/sympy/core/basic.py
--- a/sympy/core/basic.py
+++ b/sympy/core/basic.py
@@ -5,7 +5,7 @@ import decimal
import decimal
from assumptions import AssumeMeths
from sympify import sympify, SympifyError
-from cache import cache_it, cache_it_immutable, Memoizer, MemoizerArg
+from cache import cache_it_immutable, Memoizer, MemoizerArg

# from numbers import Number, Integer, Real /cyclic/
# from interval import Interval /cyclic/
@@ -1035,8 +1035,8 @@ class Basic(AssumeMeths):
# a -> c * t
if x is not None:
if not self.has(x):
- return self, []
- return S.One, [self]
+ return self, tuple()
+ return S.One, (self,)

def as_indep_terms(self, x):
coeff, terms = self.as_coeff_terms()
@@ -1053,8 +1053,8 @@ class Basic(AssumeMeths):
# a -> c + f
if x is not None:
if not self.has(x):
- return self, []
- return S.Zero, [self]
+ return self, tuple()
+ return S.Zero, (self,)

def as_numer_denom(self):
# a/b -> a,b
diff --git a/sympy/core/function.py b/sympy/core/function.py
--- a/sympy/core/function.py
+++ b/sympy/core/function.py
@@ -33,7 +33,7 @@ from basic import BasicType, BasicMeta
from basic import BasicType, BasicMeta
from methods import ArithMeths, RelMeths
from operations import AssocOp
-from cache import cache_it
+from cache import cache_it_immutable

from numbers import Rational
from symbol import Symbol
@@ -81,7 +81,7 @@ class Function(Basic, ArithMeths, RelMet

nargs = None

- @cache_it
+ @cache_it_immutable
def __new__(cls, *args, **options):
# NOTE: this __new__ is twofold:
#
diff --git a/sympy/core/mul.py b/sympy/core/mul.py
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -2,7 +2,7 @@ from basic import Basic, S, C, sympify
from basic import Basic, S, C, sympify
from operations import AssocOp
from methods import RelMeths, ArithMeths
-from cache import cache_it, cache_it_immutable
+from cache import cache_it_immutable

from symbol import Symbol, Wild
# from function import WildFunction /cyclic/
@@ -189,7 +189,7 @@ class Mul(AssocOp, RelMeths, ArithMeths)
if coeff == -1:
return None
elif coeff < 0:
- return (-coeff)**e * Mul(*([S.NegativeOne] +rest))**e
+ return (-coeff)**e * Mul(*((S.NegativeOne,) +rest))**e
else:
return coeff**e * Mul(*[s**e for s in rest])

@@ -224,7 +224,7 @@ class Mul(AssocOp, RelMeths, ArithMeths)
if coeff.is_negative:
coeff = -coeff
if coeff is not S.One:
- terms.insert(0, coeff)
+ terms = (coeff,) + terms
if isinstance(terms, Basic):
terms = terms.args
r = '-' + '*'.join([t.tostr(precedence) for t in terms])
@@ -257,13 +257,13 @@ class Mul(AssocOp, RelMeths, ArithMeths)
return '(%s)' % r
return r

- @cache_it
+ @cache_it_immutable
def as_two_terms(self):
if len(self.args) == 1:
return S.One, self
return self.args[0], Mul(*self.args[1:])

- @cache_it
+ @cache_it_immutable
def as_coeff_terms(self, x=None):
if x is not None:
l1 = []
@@ -273,11 +273,11 @@ class Mul(AssocOp, RelMeths, ArithMeths)
l2.append(f)
else:
l1.append(f)
- return Mul(*l1), l2
+ return Mul(*l1), tuple(l2)
coeff = self.args[0]
if isinstance(coeff, Number):
- return coeff, list(self.args[1:])
- return S.One, list(self.args[:])
+ return coeff, self.args[1:]
+ return S.One, self.args

@staticmethod
def _expandsums(sums):
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -154,7 +154,7 @@ class Number(Atom, RelMeths, ArithMeths)

def as_coeff_terms(self, x=None):
# a -> c * t
- return self, []
+ return self, tuple()

decimal_to_Number_cls = {
decimal.Decimal('0').as_tuple():'Zero',
@@ -454,7 +454,7 @@ class Rational(Number):
obj = Basic.__new__(cls)
obj.p = p
obj.q = q
- #obj._args = [p, q]
+ #obj._args = (p, q)
return obj

def _hashable_content(self):
diff --git a/sympy/core/operations.py b/sympy/core/operations.py
--- a/sympy/core/operations.py
+++ b/sympy/core/operations.py
@@ -1,7 +1,7 @@

from basic import Basic, S, C
from sympify import _sympify
-from cache import cache_it, cache_it_immutable
+from cache import cache_it_immutable

# from add import Add /cyclic/
# from mul import Mul /cyclic/
diff --git a/sympy/core/power.py b/sympy/core/power.py
--- a/sympy/core/power.py
+++ b/sympy/core/power.py
@@ -2,7 +2,7 @@ from basic import Basic, S, C
from basic import Basic, S, C
from sympify import _sympify
from methods import ArithMeths, RelMeths
-from cache import cache_it, cache_it_immutable
+from cache import cache_it_immutable

from symbol import Symbol, Wild, Temporary
# from numbers import Number, Rational, Integer /cyclic/
diff --git a/sympy/core/symbol.py b/sympy/core/symbol.py
--- a/sympy/core/symbol.py
+++ b/sympy/core/symbol.py
@@ -1,7 +1,7 @@

from basic import Basic, Atom, S, C, sympify
from methods import RelMeths, ArithMeths
-from cache import cache_it, cache_it_nondummy
+from cache import cache_it_nondummy

# from function import Function, WildFunction /cyclic/

diff --git a/sympy/functions/elementary/exponential.py b/sympy/functions/elementary/exponential.py
--- a/sympy/functions/elementary/exponential.py
+++ b/sympy/functions/elementary/exponential.py
@@ -1,7 +1,7 @@

from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Lambda, Function, Function
-from sympy.core.cache import cache_it, cache_it_immutable
+from sympy.core.cache import cache_it_immutable

class exp(Function):

@@ -115,10 +115,10 @@ class exp(Function):
arg = self.args[0]
if x is not None:
c,f = arg.as_coeff_factors(x)
- return self.func(c), [self.func(a) for a in f.args]
+ return self.func(c), tuple( self.func(a) for a in f.args )
if isinstance(arg, C.Add):
- return S.One, [self.func(a) for a in arg.args]
- return S.One,[self]
+ return S.One, tuple( self.func(a) for a in arg.args )
+ return S.One,(self,)

def _eval_subs(self, old, new):
if self==old: return new
diff --git a/sympy/functions/elementary/hyperbolic.py b/sympy/functions/elementary/hyperbolic.py
--- a/sympy/functions/elementary/hyperbolic.py
+++ b/sympy/functions/elementary/hyperbolic.py
@@ -1,7 +1,7 @@

from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Function, Lambda
-from sympy.core.cache import cache_it, cache_it_immutable
+from sympy.core.cache import cache_it_immutable

###############################################################################
########################### HYPERBOLIC FUNCTIONS ##############################
diff --git a/sympy/functions/elementary/integers.py b/sympy/functions/elementary/integers.py
--- a/sympy/functions/elementary/integers.py
+++ b/sympy/functions/elementary/integers.py
@@ -84,7 +84,7 @@ class floor(Function):
return -ceiling(-arg)
else:
return cls(arg.evalf())
- elif terms == [ S.ImaginaryUnit ] and coeff.is_real:
+ elif terms == ( S.ImaginaryUnit, ) and coeff.is_real:
return cls(coeff)*S.ImaginaryUnit

def _eval_is_bounded(self):
@@ -174,7 +174,7 @@ class ceiling(Function):
return -floor(-arg)
else:
return cls(arg.evalf())
- elif terms == [ S.ImaginaryUnit ] and coeff.is_real:
+ elif terms == ( S.ImaginaryUnit, ) and coeff.is_real:
return cls(coeff)*S.ImaginaryUnit

def _eval_is_bounded(self):
diff --git a/sympy/functions/elementary/trigonometric.py b/sympy/functions/elementary/trigonometric.py
--- a/sympy/functions/elementary/trigonometric.py
+++ b/sympy/functions/elementary/trigonometric.py
@@ -2,7 +2,7 @@ from sympy.core.basic import Basic, S, C
from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Lambda, Function
from miscellaneous import sqrt
-from sympy.core.cache import cache_it, cache_it_immutable
+from sympy.core.cache import cache_it_immutable

###############################################################################
########################## TRIGONOMETRIC FUNCTIONS ############################
diff --git a/sympy/functions/special/error_functions.py b/sympy/functions/special/error_functions.py
--- a/sympy/functions/special/error_functions.py
+++ b/sympy/functions/special/error_functions.py
@@ -2,7 +2,7 @@ from sympy.core.basic import Basic, S, C
from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Function
from sympy.functions.elementary.miscellaneous import sqrt
-from sympy.core.cache import cache_it, cache_it_immutable
+from sympy.core.cache import cache_it_immutable

###############################################################################
################################ ERROR FUNCTION ###############################
diff --git a/sympy/series/limits_series.py b/sympy/series/limits_series.py
--- a/sympy/series/limits_series.py
+++ b/sympy/series/limits_series.py
@@ -10,7 +10,7 @@

from sympy.core.basic import Basic, S, C, sympify
from sympy.core.methods import RelMeths, ArithMeths
-from sympy.core.cache import cache_it, cache_it_immutable
+from sympy.core.cache import cache_it_immutable

class Limit(Basic, RelMeths, ArithMeths):
""" Find the limit of the expression under process x->xlim.
@@ -76,10 +76,11 @@ class Limit(Basic, RelMeths, ArithMeths)
return r

class InfLimit(Basic):
+ _xoo = C.Symbol('xoo', dummy=True, unbounded=True, positive=True)
+
@staticmethod
- @cache_it_immutable
def limit_process_symbol():
- return C.Symbol('xoo', dummy=True, unbounded=True, positive=True)
+ return InfLimit._xoo

@cache_it_immutable
def __new__(cls, expr, x):
diff --git a/sympy/series/order.py b/sympy/series/order.py
--- a/sympy/series/order.py
+++ b/sympy/series/order.py
@@ -1,7 +1,7 @@ from sympy.core.basic import Basic, S, C
from sympy.core.basic import Basic, S, C, sympify
from sympy.core import oo, Rational, Pow
from sympy.core.methods import ArithMeths, RelMeths
-from sympy.core.cache import cache_it, cache_it_immutable
+from sympy.core.cache import cache_it_immutable

class Order(Basic, ArithMeths, RelMeths):
"""

Kirill Smelkov

unread,
Feb 10, 2008, 3:36:44 PM2/10/08
to sympy-...@googlegroups.com
# HG changeset patch
# User Kirill Smelkov <ki...@landau.phys.spbu.ru>
# Date 1202675632 -10800
# Node ID 62f59270ce7edc6d194c49ff9c3faadafdd84a10
# Parent 2b33a742b1cead7b36506727e2cad2e37831f5fb
[3/5] cache cleanup, hadle only immutable objects

Now we've killed cacheit_fast, we have the only one usable cache_it_immutable,
and let's rename it to just `cacheit`

This patch touches only cache.py internals. I'll convert the rest of sympy in
the following commit.

diff --git a/sympy/core/cache.py b/sympy/core/cache.py
--- a/sympy/core/cache.py
+++ b/sympy/core/cache.py

@@ -44,12 +44,34 @@ def clear_cache():

########################################

-def cache_it_nocache(func):
+def __cacheit_nocache(func):
return func



-def cache_it_immutable(func):
+def __cacheit(func):
+ """caching decorator.
+
+ important: the result of cached function must be *immutable*
+
+
+ Example
+ -------
+
+ @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'
+ """
+
func._cache_it_cache = func_cache_it_cache = {}
CACHE.append((func, func_cache_it_cache))

@@ -70,9 +92,9 @@ def cache_it_immutable(func):
return wrapper


-def cache_it_debug(func):
- """cache_it_fast + code to check cache consitency"""
- cfunc = __cache_it_immutable(func)
+def __cacheit_debug(func):
+ """cacheit + code to check cache consitency"""
+ cfunc = __cacheit(func)



def wrapper(*args, **kw_args):
# always call function itself and compare it with cached version

@@ -91,7 +113,7 @@ def cache_it_debug(func):
return wrapper


-def cache_it_nondummy(func):
+def __cacheit_nondummy(func):
func._cache_it_cache = func_cache_it_cache = {}
CACHE.append((func, func_cache_it_cache))

@@ -284,16 +306,11 @@ usecache = os.getenv('SYMPY_USE_CACHE',

usecache = os.getenv('SYMPY_USE_CACHE', 'yes').lower()

if usecache=='no':

- cache_it_immutable = cache_it_nocache
- cache_it_debug = cache_it_nocache
- cache_it_nondummy = cache_it_nocache
Memoizer = Memoizer_nocache
- cache_it = cache_it_nocache
+ cacheit = __cacheit_nocache
elif usecache=='yes':
- cache_it = cache_it_immutable
+ cacheit = __cacheit
elif usecache=='debug':
- cache_it = cache_it_debug # twice slower
- __cache_it_immutable = cache_it_immutable
- cache_it_immutable = cache_it_debug
+ cacheit = __cacheit_debug # a lot slower

Kirill Smelkov

unread,
Feb 10, 2008, 3:36:45 PM2/10/08
to sympy-...@googlegroups.com
# HG changeset patch
# User Kirill Smelkov <ki...@landau.phys.spbu.ru>
# Date 1202675633 -10800
# Node ID d4ca483416af0bcbbe5c533590f25b5be9b5b9d2
# Parent 62f59270ce7edc6d194c49ff9c3faadafdd84a10
[4/5] cache cleanup, hadle only immutable objects

cache_it_immutable -> cacheit all over the place

diff --git a/sympy/core/add.py b/sympy/core/add.py
--- a/sympy/core/add.py
+++ b/sympy/core/add.py
@@ -2,7 +2,7 @@ from basic import Basic, S, C
from basic import Basic, S, C
from operations import AssocOp
from methods import RelMeths, ArithMeths

-from cache import cache_it_immutable
+from cache import cacheit



from symbol import Symbol, Wild, Temporary
# from numbers import Number /cyclic/
@@ -111,7 +111,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
return '(%s)' % r
return r

- @cache_it_immutable
+ @cacheit


def as_coeff_factors(self, x=None):
if x is not None:
l1 = []

@@ -143,7 +143,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
def _combine_inverse(lhs, rhs):
return lhs - rhs

- @cache_it_immutable
+ @cacheit


def as_two_terms(self):
if len(self.args) == 1:
return S.Zero, self

@@ -278,7 +278,7 @@ class Add(AssocOp, RelMeths, ArithMeths)
def _eval_oseries(self, order):
return Add(*[f.oseries(order) for f in self.args])

- @cache_it_immutable
+ @cacheit


def extract_leading_order(self, *symbols):
lst = []
seq = [(f, C.Order(f, *symbols)) for f in self.args]

diff --git a/sympy/core/basic.py b/sympy/core/basic.py
--- a/sympy/core/basic.py
+++ b/sympy/core/basic.py
@@ -5,7 +5,7 @@ import decimal
import decimal
from assumptions import AssumeMeths
from sympify import sympify, SympifyError

-from cache import cache_it_immutable, Memoizer, MemoizerArg
+from cache import cacheit, Memoizer, MemoizerArg



# from numbers import Number, Integer, Real /cyclic/
# from interval import Interval /cyclic/

@@ -450,7 +450,7 @@ class Basic(AssumeMeths):
return new
return self

- @cache_it_immutable
+ @cacheit
def subs(self, old, new):
"""Substitutes an expression old -> new."""
old = sympify(old)
@@ -743,7 +743,7 @@ class Basic(AssumeMeths):
l.sort()
return [(s,e) for i,s,e in l]

- @cache_it_immutable
+ @cacheit
def count_ops(self, symbolic=True):
""" Return the number of operations in expressions.

@@ -1200,7 +1200,7 @@ class Basic(AssumeMeths):
return self
return r + o

- @cache_it_immutable
+ @cacheit
def oseries(self, order):
"""
Return the series of an expression upto given Order symbol (without the
@@ -1282,7 +1282,7 @@ class Basic(AssumeMeths):
from sympy.series.limits_series import InfLimit
return InfLimit(self, x)

- @cache_it_immutable
+ @cacheit
def as_leading_term(self, *symbols):
if len(symbols)>1:
c = self


diff --git a/sympy/core/function.py b/sympy/core/function.py
--- a/sympy/core/function.py
+++ b/sympy/core/function.py
@@ -33,7 +33,7 @@ from basic import BasicType, BasicMeta
from basic import BasicType, BasicMeta
from methods import ArithMeths, RelMeths
from operations import AssocOp

-from cache import cache_it_immutable
+from cache import cacheit



from numbers import Rational
from symbol import Symbol
@@ -81,7 +81,7 @@ class Function(Basic, ArithMeths, RelMet

nargs = None

- @cache_it_immutable
+ @cacheit


def __new__(cls, *args, **options):
# NOTE: this __new__ is twofold:
#
diff --git a/sympy/core/mul.py b/sympy/core/mul.py
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -2,7 +2,7 @@ from basic import Basic, S, C, sympify
from basic import Basic, S, C, sympify
from operations import AssocOp
from methods import RelMeths, ArithMeths

-from cache import cache_it_immutable
+from cache import cacheit



from symbol import Symbol, Wild
# from function import WildFunction /cyclic/

@@ -257,13 +257,13 @@ class Mul(AssocOp, RelMeths, ArithMeths)
return '(%s)' % r
return r

- @cache_it_immutable
+ @cacheit


def as_two_terms(self):
if len(self.args) == 1:
return S.One, self
return self.args[0], Mul(*self.args[1:])

- @cache_it_immutable
+ @cacheit


def as_coeff_terms(self, x=None):
if x is not None:
l1 = []

@@ -367,7 +367,7 @@ class Mul(AssocOp, RelMeths, ArithMeths)
denoms.append(d)
return Mul(*numers), Mul(*denoms)

- @cache_it_immutable
+ @cacheit
def count_ops(self, symbolic=True):
if symbolic:
return Add(*[t.count_ops(symbolic) for t in self[:]]) + Symbol('MUL') * (len(self[:])-1)


diff --git a/sympy/core/operations.py b/sympy/core/operations.py
--- a/sympy/core/operations.py
+++ b/sympy/core/operations.py
@@ -1,7 +1,7 @@

from basic import Basic, S, C
from sympify import _sympify

-from cache import cache_it_immutable
+from cache import cacheit



# from add import Add /cyclic/
# from mul import Mul /cyclic/

@@ -17,7 +17,7 @@ class AssocOp(Basic):
Base class for Add and Mul.
"""

- @cache_it_immutable
+ @cacheit
def __new__(cls, *args, **assumptions):
if len(args)==0:
return cls.identity()


diff --git a/sympy/core/power.py b/sympy/core/power.py
--- a/sympy/core/power.py
+++ b/sympy/core/power.py
@@ -2,7 +2,7 @@ from basic import Basic, S, C
from basic import Basic, S, C
from sympify import _sympify
from methods import ArithMeths, RelMeths

-from cache import cache_it_immutable
+from cache import cacheit



from symbol import Symbol, Wild, Temporary
# from numbers import Number, Rational, Integer /cyclic/

@@ -54,7 +54,7 @@ class Pow(Basic, ArithMeths, RelMeths):

precedence = Basic.Pow_precedence

- @cache_it_immutable
+ @cacheit
def __new__(cls, a, b, **assumptions):
a = _sympify(a)
b = _sympify(b)
@@ -391,7 +391,7 @@ class Pow(Basic, ArithMeths, RelMeths):
s = d[r] = Temporary()
return s

- @cache_it_immutable
+ @cacheit
def count_ops(self, symbolic=True):
if symbolic:
return Add(*[t.count_ops(symbolic) for t in self[:]]) + Symbol('POW')
@@ -504,7 +504,7 @@ class Pow(Basic, ArithMeths, RelMeths):
return self.base.as_leading_term(x) ** self.exp
return C.exp(self.exp * C.log(self.base)).as_leading_term(x)

- @cache_it_immutable
+ @cacheit
def taylor_term(self, n, x, *previous_terms): # of (1+x)**e
if n<0: return S.Zero
x = _sympify(x)


diff --git a/sympy/functions/elementary/exponential.py b/sympy/functions/elementary/exponential.py
--- a/sympy/functions/elementary/exponential.py
+++ b/sympy/functions/elementary/exponential.py
@@ -1,7 +1,7 @@

from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Lambda, Function, Function

-from sympy.core.cache import cache_it_immutable
+from sympy.core.cache import cacheit

class exp(Function):

@@ -88,7 +88,7 @@ class exp(Function):
return C.Mul(*(excluded+[cls(C.Add(*included))]))

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n<0: return S.Zero
if n==0: return S.One
@@ -318,7 +318,7 @@ class log(Function):
return x.is_unbounded

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms): # of log(1+x)
if n<0: return S.Zero
x = sympify(x)


diff --git a/sympy/functions/elementary/hyperbolic.py b/sympy/functions/elementary/hyperbolic.py
--- a/sympy/functions/elementary/hyperbolic.py
+++ b/sympy/functions/elementary/hyperbolic.py
@@ -1,7 +1,7 @@

from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Function, Lambda

-from sympy.core.cache import cache_it_immutable
+from sympy.core.cache import cacheit



###############################################################################
########################### HYPERBOLIC FUNCTIONS ##############################

@@ -51,7 +51,7 @@ class sinh(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -147,7 +147,7 @@ class cosh(Function):
return cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 1:
return S.Zero
@@ -243,7 +243,7 @@ class tanh(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -341,7 +341,7 @@ class coth(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n == 0:
return 1 / sympify(x)
@@ -437,7 +437,7 @@ class asinh(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -510,7 +510,7 @@ class acosh(Function):
return cst_table[arg]*S.ImaginaryUnit

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n == 0:
return S.Pi*S.ImaginaryUnit / 2
@@ -579,7 +579,7 @@ class atanh(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -640,7 +640,7 @@ class acoth(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n == 0:
return S.Pi*S.ImaginaryUnit / 2


diff --git a/sympy/functions/elementary/trigonometric.py b/sympy/functions/elementary/trigonometric.py
--- a/sympy/functions/elementary/trigonometric.py
+++ b/sympy/functions/elementary/trigonometric.py
@@ -2,7 +2,7 @@ from sympy.core.basic import Basic, S, C
from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Lambda, Function
from miscellaneous import sqrt

-from sympy.core.cache import cache_it_immutable
+from sympy.core.cache import cacheit



###############################################################################
########################## TRIGONOMETRIC FUNCTIONS ############################

@@ -72,7 +72,7 @@ class sin(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -207,7 +207,7 @@ class cos(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 1:
return S.Zero
@@ -344,7 +344,7 @@ class tan(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -467,7 +467,7 @@ class cot(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n == 0:
return 1 / sympify(x)
@@ -582,7 +582,7 @@ class asin(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -659,7 +659,7 @@ class acos(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n == 0:
return S.Pi / 2
@@ -748,7 +748,7 @@ class atan(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero
@@ -821,7 +821,7 @@ class acot(Function):


@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n == 0:
return S.Pi / 2 # FIX THIS


diff --git a/sympy/functions/special/error_functions.py b/sympy/functions/special/error_functions.py
--- a/sympy/functions/special/error_functions.py
+++ b/sympy/functions/special/error_functions.py
@@ -2,7 +2,7 @@ from sympy.core.basic import Basic, S, C
from sympy.core.basic import Basic, S, C, sympify
from sympy.core.function import Function
from sympy.functions.elementary.miscellaneous import sqrt

-from sympy.core.cache import cache_it_immutable
+from sympy.core.cache import cacheit



###############################################################################
################################ ERROR FUNCTION ###############################

@@ -44,7 +44,7 @@ class erf(Function):
return -cls(-arg)

@staticmethod
- @cache_it_immutable
+ @cacheit
def taylor_term(n, x, *previous_terms):
if n < 0 or n % 2 == 0:
return S.Zero


diff --git a/sympy/series/limits_series.py b/sympy/series/limits_series.py
--- a/sympy/series/limits_series.py
+++ b/sympy/series/limits_series.py

@@ -10,14 +10,14 @@



from sympy.core.basic import Basic, S, C, sympify
from sympy.core.methods import RelMeths, ArithMeths

-from sympy.core.cache import cache_it_immutable
+from sympy.core.cache import cacheit



class Limit(Basic, RelMeths, ArithMeths):
""" Find the limit of the expression under process x->xlim.

Limit(expr, x, xlim)
"""
- @cache_it_immutable
+ @cacheit
def __new__(cls, expr, x, xlim, direction='<', **assumptions):
expr = sympify(expr)
x = sympify(x)
@@ -82,7 +82,7 @@ class InfLimit(Basic):
def limit_process_symbol():
return InfLimit._xoo

- @cache_it_immutable
+ @cacheit
def __new__(cls, expr, x):
expr = orig_expr = sympify(expr)
orig_x = sympify(x)
@@ -139,7 +139,7 @@ class InfLimit(Basic):

return result

-@cache_it_immutable
+@cacheit
def mrv_inflimit(expr, x, _cache = {}):
if _cache.has_key((expr, x)):
raise RuntimeError('Detected recursion while computing mrv_inflimit(%s, %s)' % (expr, x))
@@ -172,11 +172,11 @@ def mrv_inflimit(expr, x, _cache = {}):
return C.sign(c) * S.Infinity
raise RuntimeError('Failed to compute mrv_inflimit(%s, %s), got lt=%s' % (self, x, lt))

-@cache_it_immutable
+@cacheit
def cmp_ops_count(e1,e2):
return cmp(e1.count_ops(symbolic=False), e2.count_ops(symbolic=False))

-@cache_it_immutable
+@cacheit
def mrv_compare(f, g, x):
log = C.log
if isinstance(f, C.exp): f = f.args[0]


diff --git a/sympy/series/order.py b/sympy/series/order.py
--- a/sympy/series/order.py
+++ b/sympy/series/order.py
@@ -1,7 +1,7 @@ from sympy.core.basic import Basic, S, C
from sympy.core.basic import Basic, S, C, sympify
from sympy.core import oo, Rational, Pow
from sympy.core.methods import ArithMeths, RelMeths

-from sympy.core.cache import cache_it_immutable
+from sympy.core.cache import cacheit

class Order(Basic, ArithMeths, RelMeths):
"""
@@ -81,7 +81,7 @@ class Order(Basic, ArithMeths, RelMeths)

_cache = {}

- @cache_it_immutable
+ @cacheit
def __new__(cls, expr, *symbols, **assumptions):
expr = sympify(expr).expand(trig=True)
if expr is S.NaN:
@@ -268,7 +268,7 @@ class Order(Basic, ArithMeths, RelMeths)
order_symbols = order_symbols + (s,)
return self.expr, order_symbols

- @cache_it_immutable
+ @cacheit
def contains(self, expr):
"""
Return True if expr belongs to Order(self.expr, *self.symbols).

Ondrej Certik

unread,
Feb 10, 2008, 4:41:52 PM2/10/08
to sympy-...@googlegroups.com
> 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 ummutable
> + # this is not 100% check, but at least we catch lists usage
> + hash(r1), hash(r2)
> +
> + # also see if returned values are the same
> assert r1 == r2
> +
> return r1

What is the meaning of adding hash(r1), hash(r2) ? This should be
explained in the comments. Also I suggest to reword it:

# this check doesn't work 100%, ...

Is it supposed to trigger r1 and r2 to calculate and store their
caches? If so, it should be written in the comments. Otherwise, this
is an empty statement. :)

>
> cache_it was created to cache mutable results, but there is no reason for it.
>
> first, to ensure cache itself is kept unmodified from outside, each time return
> value is deep-copied and this adds overhead.
>
> second, we have no reason to cache mutable instances -- what we almost always
> have is immutable, so why have additional unneccesary overhead (deep copy)?
>
> Very seldom cache_it user which were returning mutable things (like
> .as_coeff_factors) were rewrittend to return immutable objects. The performance

^^ What is the meaning of this sentence? :) I think there are some
words missing in it?


Otherwise all patches are ok, feel free to commit, after fixing the above.

Ondrej

Kirill Smelkov

unread,
Feb 10, 2008, 4:49:45 PM2/10/08
to sympy-...@googlegroups.com
On Sun, Feb 10, 2008 at 10:41:52PM +0100, Ondrej Certik wrote:
>
> > 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 ummutable
> > + # this is not 100% check, but at least we catch lists usage
> > + hash(r1), hash(r2)
> > +
> > + # also see if returned values are the same
> > assert r1 == r2
> > +
> > return r1
>
> What is the meaning of adding hash(r1), hash(r2) ? This should be
> explained in the comments. Also I suggest to reword it:
>
> # this check doesn't work 100%, ...
>
> Is it supposed to trigger r1 and r2 to calculate and store their
> caches? If so, it should be written in the comments. Otherwise, this
> is an empty statement. :)

This is a try to catch mutable objects:


In [1]: l = [1,2,3]

In [2]: hash(l)
---------------------------------------------------------------------------
exceptions.TypeError Traceback (most recent call last)
TypeError: list objects are unhashable

In [3]: d = {'a':1, 'b':2}

In [4]: hash(d)
---------------------------------------------------------------------------
exceptions.TypeError Traceback (most recent call last)
TypeError: dict objects are unhashable

In [8]: t = ([1,2], 3)

In [9]: hash(t)
---------------------------------------------------------------------------
exceptions.TypeError Traceback (most recent call last)
TypeError: list objects are unhashable


In [5]: t = (1,2,3)

In [6]: hash(t)
Out[6]: -378539185


>
> >
> > cache_it was created to cache mutable results, but there is no reason for it.
> >
> > first, to ensure cache itself is kept unmodified from outside, each time return
> > value is deep-copied and this adds overhead.
> >
> > second, we have no reason to cache mutable instances -- what we almost always
> > have is immutable, so why have additional unneccesary overhead (deep copy)?
> >
> > Very seldom cache_it user which were returning mutable things (like
> > .as_coeff_factors) were rewrittend to return immutable objects. The performance
>
> ^^ What is the meaning of this sentence? :) I think there are some
> words missing in it?

It means "I'm too sleepy" :)
Sorry, I'll reword it tomorrow...

> Otherwise all patches are ok, feel free to commit, after fixing the above.

Yep, I'll add comments about hash(r1) and reword tommorrow.

Thanks for review.

--
Всего хорошего, Кирилл.
http://landau.phys.spbu.ru/~kirr/aiv/

Kirill Smelkov

unread,
Feb 11, 2008, 4:13:45 AM2/11/08
to sympy-...@googlegroups.com

All patches are in.

btw:

pushing to ssh://h...@hg.sympy.org/sympy
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 5 changesets with 30 changes to 16 files
remote: error: incoming.notify hook raised an exception:
remote: error: incoming.notify hook raised an exception:

Ondrej Certik

unread,
Feb 11, 2008, 4:16:12 AM2/11/08
to sympy-...@googlegroups.com

I know, but it doesn't print any useful info and I don't know how to
get to know what is wrong. If you know, let me know.

Ondrej

Kirill Smelkov

unread,
Feb 11, 2008, 4:27:35 AM2/11/08
to sympy-...@googlegroups.com

I've tried to reproduce it locally, but everything run without an
exception. I'm on hg crew (e2cbdd931341) though.

Reply all
Reply to author
Forward
0 new messages