"""
There are two types of functions:
1) defined function like exp or sin that has a name and body
(in the sense that function can be evaluated).
e = exp
2) undefined function with a name but no body. Undefined
functions can be defined using a Function class as follows:
f = Function('f')
(the result will be a Function instance)
3) this isn't implemented yet: anonymous function or lambda function that has
no name but has body with dummy variables. Examples of anonymous function
creation:
f = Lambda(x, exp(x)*x)
f = Lambda(exp(x)*x) # free symbols in the expression define the number of arguments
f = exp * Lambda(x,x)
4) isn't implemented yet: composition of functions, like (sin+cos)(x), this
works in sympy core, but needs to be ported back to SymPy.
Example:
>>> import sympy
>>> f = sympy.Function("f")
>>> from sympy.abc import x
>>> f(x)
f(x)
>>> print sympy.srepr(f(x).func)
Function('f')
>>> f(x).args
(x,)
"""
from core import BasicMeta, C
from basic import Basic
from singleton import S
from expr import Expr, AtomicExpr
from decorators import _sympifyit
from compatibility import iterable,is_sequence
from cache import cacheit
from numbers import Rational
from sympy.core.containers import Tuple
from sympy.core.decorators import deprecated
from sympy.utilities import default_sort_key
from sympy.utilities.iterables import uniq
from sympy import mpmath
import sympy.mpmath.libmp as mlib
[docs]class PoleError(Exception):
pass
class ArgumentIndexError(ValueError):
def __str__(self):
return ("Invalid operation with argument number %s for Function %s" %
(self.args[1], self.args[0]))
[docs]class FunctionClass(BasicMeta):
"""
Base class for function classes. FunctionClass is a subclass of type.
Use Function('<function name>' [ , signature ]) to create
undefined function classes.
"""
__metaclass__ = BasicMeta
_new = type.__new__
def __repr__(cls):
return cls.__name__
def __contains__(self, obj):
return (self == obj)
class Application(Basic):
"""
Base class for applied functions.
Instances of Application represent the result of applying an application of
any type to any object.
"""
__metaclass__ = FunctionClass
__slots__ = []
is_Function = True
nargs = None
@cacheit
def __new__(cls, *args, **options):
args = map(sympify, args)
# these lines should be refactored
for opt in ["nargs", "dummy", "comparable", "noncommutative", "commutative"]:
if opt in options:
del options[opt]
if options.pop('evaluate', True):
evaluated = cls.eval(*args)
if evaluated is not None:
return evaluated
return super(Application, cls).__new__(cls, *args, **options)
@classmethod
def eval(cls, *args):
"""
Returns a canonical form of cls applied to arguments args.
The eval() method is called when the class cls is about to be
instantiated and it should return either some simplified instance
(possible of some other class), or if the class cls should be
unmodified, return None.
Example of eval() for the function "sign"
---------------------------------------------
@classmethod
def eval(cls, arg):
if arg is S.NaN:
return S.NaN
if arg is S.Zero: return S.Zero
if arg.is_positive: return S.One
if arg.is_negative: return S.NegativeOne
if isinstance(arg, C.Mul):
coeff, terms = arg.as_coeff_mul()
if coeff is not S.One:
return cls(coeff) * cls(arg._new_rawargs(*terms))
"""
return
@property
def func(self):
return self.__class__
def _eval_subs(self, old, new):
if self == old:
return new
elif old.is_Function and new.is_Function:
if old == self.func:
nargs = len(self.args)
if (nargs == new.nargs or new.nargs is None or
(isinstance(new.nargs, tuple) and nargs in new.nargs)):
return new(*self.args)
return self.func(*[s.subs(old, new) for s in self.args])
def __contains__(self, obj):
if self.func == obj:
return True
return super(Application, self).__contains__(obj)
[docs]class Function(Application, Expr):
"""
Base class for applied numeric functions.
Constructor of undefined function classes.
"""
@cacheit
def __new__(cls, *args, **options):
# Handle calls like Function('f')
if cls is Function:
return UndefinedFunction(*args)
if cls.nargs is not None:
if (isinstance(cls.nargs, tuple) and len(args) not in cls.nargs) \
or (not isinstance(cls.nargs, tuple) and cls.nargs != len(args)):
raise ValueError('Function %s expects %s argument(s), got %s.' % (
cls, cls.nargs, len(args)))
args = map(sympify, args)
evaluate = options.pop('evaluate', True)
if evaluate:
evaluated = cls.eval(*args)
if evaluated is not None:
return evaluated
result = super(Application, cls).__new__(cls, *args, **options)
pr = max(cls._should_evalf(a) for a in args)
pr2 = min(cls._should_evalf(a) for a in args)
if evaluate and pr2 > 0:
return result.evalf(mlib.libmpf.prec_to_dps(pr))
return result
@classmethod
def _should_evalf(cls, arg):
"""
Decide if the function should automatically evalf().
By default (in this implementation), this happens if (and only if) the
ARG is a floating point number.
This function is used by __new__.
"""
if arg.is_Float:
return arg._prec
if not arg.is_Add:
return -1
re, im = arg.as_real_imag()
l = [a._prec for a in [re, im] if a.is_Float]
l.append(-1)
return max(l)
@classmethod
def class_key(cls):
funcs = {
'exp': 10, 'log': 11,
'sin': 20, 'cos': 21, 'tan': 22, 'cot': 23,
'sinh': 30, 'cosh': 31, 'tanh': 32, 'coth': 33,
}
name = cls.__name__
try:
i = funcs[name]
except KeyError:
nargs = cls.nargs
if nargs is None:
i = 0
else:
i = 10000
return 4, i, name
@property
def is_commutative(self):
if all(getattr(t, 'is_commutative') for t in self.args):
return True
else:
return False
@classmethod
@deprecated
def canonize(cls, *args):
return cls.eval(*args)
def _eval_evalf(self, prec):
# Lookup mpmath function based on name
fname = self.func.__name__
try:
if not hasattr(mpmath, fname):
from sympy.utilities.lambdify import MPMATH_TRANSLATIONS
fname = MPMATH_TRANSLATIONS[fname]
func = getattr(mpmath, fname)
except (AttributeError, KeyError):
try:
return C.Float(self._imp_(*self.args), prec)
except (AttributeError, TypeError):
return
# Convert all args to mpf or mpc
try:
args = [arg._to_mpmath(prec) for arg in self.args]
except ValueError:
return
# Set mpmath precision and apply. Make sure precision is restored
# afterwards
orig = mpmath.mp.prec
try:
mpmath.mp.prec = prec
v = func(*args)
finally:
mpmath.mp.prec = orig
return Expr._from_mpmath(v, prec)
def _eval_is_comparable(self):
if self.is_Function:
r = True
for s in self.args:
c = s.is_comparable
if c is None: return
if not c: r = False
return r
return
def _eval_derivative(self, s):
# f(x).diff(s) -> x.diff(s) * f.fdiff(1)(s)
i = 0
l = []
for a in self.args:
i += 1
da = a.diff(s)
if da is S.Zero:
continue
try:
df = self.fdiff(i)
except ArgumentIndexError:
df = Function.fdiff(self, i)
l.append(df * da)
return Add(*l)
def _eval_is_commutative(self):
r = True
for a in self._args:
c = a.is_commutative
if c is None: return None
if not c: r = False
return r
def as_base_exp(self):
return self, S.One
def _eval_aseries(self, n, args0, x, logx):
"""
Compute an asymptotic expansion around args0, in terms of self.args.
This function is only used internally by _eval_nseries and should not
be called directly; derived classes can overwrite this to implement
asymptotic expansions.
"""
raise PoleError('Asymptotic expansion of %s around %s '
'not implemented.' % (type(self), args0))
def _eval_nseries(self, x, n, logx):
"""
This function does compute series for multivariate functions,
but the expansion is always in terms of *one* variable.
Examples:
>>> from sympy import atan2, O
>>> from sympy.abc import x, y
>>> atan2(x, y).series(x, n=2)
atan2(0, y) + x/y + O(x**2)
>>> atan2(x, y).series(y, n=2)
-y/x + atan2(x, 0) + O(y**2)
This function also computes asymptotic expansions, if necessary
and possible:
>>> from sympy import loggamma
>>> loggamma(1/x)._eval_nseries(x,0,None)
-1/x - log(x)/x + log(x)/2 + O(1)
"""
if self.func.nargs is None:
raise NotImplementedError('series for user-defined \
functions are not supported.')
args = self.args
args0 = [t.limit(x, 0) for t in args]
if any([t.is_bounded == False for t in args0]):
from sympy import Dummy, oo, zoo, nan
a = [t.compute_leading_term(x, logx=logx) for t in args]
a0 = [t.limit(x, 0) for t in a]
if any ([t.has(oo, -oo, zoo, nan) for t in a0]):
return self._eval_aseries(n, args0, x, logx)._eval_nseries(x, n, logx)
# Careful: the argument goes to oo, but only logarithmically so. We
# are supposed to do a power series expansion "around the
# logarithmic term". e.g.
# f(1+x+log(x))
# -> f(1+logx) + x*f'(1+logx) + O(x**2)
# where 'logx' is given in the argument
a = [t._eval_nseries(x, n, logx) for t in args]
z = [r - r0 for (r, r0) in zip(a, a0)]
p = [Dummy() for t in z]
q = []
v = None
for ai, zi, pi in zip(a0, z, p):
if zi.has(x):
if v is not None: raise NotImplementedError
q.append(ai + pi)
v = pi
else:
q.append(ai)
e1 = self.func(*q)
if v is None:
return e1
s = e1._eval_nseries(v, n, logx)
o = s.getO()
s = s.removeO()
s = s.subs(v, zi).expand() + C.Order(o.expr.subs(v, zi), x)
return s
if (self.func.nargs == 1 and args0[0]) or self.func.nargs > 1:
e = self
e1 = e.expand()
if e == e1:
#for example when e = sin(x+1) or e = sin(cos(x))
#let's try the general algorithm
term = e.subs(x, S.Zero)
if term.is_bounded is False or term is S.NaN:
raise PoleError("Cannot expand %s around 0" % (self))
series = term
fact = S.One
for i in range(n-1):
i += 1
fact *= Rational(i)
e = e.diff(x)
subs = e.subs(x, S.Zero)
if subs is S.NaN:
# try to evaluate a limit if we have to
subs = e.limit(x, S.Zero)
if subs.is_bounded is False:
raise PoleError("Cannot expand %s around 0" % (self))
term = subs*(x**i)/fact
term = term.expand()
series += term
return series + C.Order(x**n, x)
return e1.nseries(x, n=n, logx=logx)
arg = self.args[0]
l = []
g = None
for i in xrange(n+2):
g = self.taylor_term(i, arg, g)
g = g.nseries(x, n=n, logx=logx)
l.append(g)
return Add(*l) + C.Order(x**n, x)
def _eval_expand_basic(self, deep=True, **hints):
if not deep:
return self
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_basic'):
newterm = term._eval_expand_basic(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_power_exp(self, deep=True, **hints):
if not deep:
return self
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_power_exp'):
newterm = term._eval_expand_power_exp(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_power_base(self, deep=True, **hints):
if not deep:
return self
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_power_base'):
newterm = term._eval_expand_power_base(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_mul(self, deep=True, **hints):
if not deep:
return self
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_mul'):
newterm = term._eval_expand_mul(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_multinomial(self, deep=True, **hints):
if not deep:
return self
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_multinomail'):
newterm = term._eval_expand_multinomial(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_log(self, deep=True, **hints):
if not deep:
return self
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_log'):
newterm = term._eval_expand_log(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_complex(self, deep=True, **hints):
if deep:
func = self.func(*[ a.expand(deep, **hints) for a in self.args ])
else:
func = self.func(*self.args)
return C.re(func) + S.ImaginaryUnit * C.im(func)
def _eval_expand_trig(self, deep=True, **hints):
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_trig'):
newterm = term._eval_expand_trig(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_expand_func(self, deep=True, **hints):
sargs, terms = self.args, []
for term in sargs:
if hasattr(term, '_eval_expand_func'):
newterm = term._eval_expand_func(deep=deep, **hints)
else:
newterm = term
terms.append(newterm)
return self.func(*terms)
def _eval_rewrite(self, pattern, rule, **hints):
if hints.get('deep', False):
args = [ a._eval_rewrite(pattern, rule, **hints) for a in self.args ]
else:
args = self.args
if pattern is None or isinstance(self.func, pattern):
if hasattr(self, rule):
rewritten = getattr(self, rule)(*args)
if rewritten is not None:
return rewritten
return self.func(*args)
def fdiff(self, argindex=1):
if self.nargs is not None:
if isinstance(self.nargs, tuple):
nargs = self.nargs[-1]
else:
nargs = self.nargs
if not (1<=argindex<=nargs):
raise ArgumentIndexError(self, argindex)
if not self.args[argindex-1].is_Symbol:
# See issue 1525 and issue 1620 and issue 2501
arg_dummy = C.Dummy('xi_%i' % argindex)
return Subs(Derivative(
self.subs(self.args[argindex-1], arg_dummy),
arg_dummy), arg_dummy, self.args[argindex-1])
return Derivative(self,self.args[argindex-1],evaluate=False)
def _eval_as_leading_term(self, x):
"""General method for the leading term"""
# XXX This seems broken to me!
arg = self.args[0].as_leading_term(x)
if C.Order(1,x).contains(arg):
return arg
else:
return self.func(arg)
@classmethod
[docs] def taylor_term(cls, n, x, *previous_terms):
"""General method for the taylor term.
This method is slow, because it differentiates n-times. Subclasses can
redefine it to make it faster by using the "previous_terms".
"""
x = sympify(x)
return cls(x).diff(x, n).subs(x, 0) * x**n / C.factorial(n)
class AppliedUndef(Function):
"""
Base class for expressions resulting from the application of an undefined function.
"""
def __new__(cls, *args, **options):
args = map(sympify, args)
result = Expr.__new__(cls, *args, **options)
result.nargs = len(args)
return result
class UndefinedFunction(FunctionClass):
"""
The (meta)class of undefined functions.
"""
def __new__(mcl, name):
return BasicMeta.__new__(mcl, name, (AppliedUndef,), {})
[docs]class WildFunction(Function, AtomicExpr):
"""
WildFunction() matches any expression but another WildFunction()
XXX is this as intended, does it work ?
"""
nargs = 1
def __new__(cls, name, **assumptions):
obj = Function.__new__(cls, name, **assumptions)
obj.name = name
return obj
def matches(self, expr, repl_dict={}, evaluate=False):
if self in repl_dict:
if repl_dict[self] == expr:
return repl_dict
else:
return None
if self.nargs is not None:
if not hasattr(expr,'nargs') or self.nargs != expr.nargs:
return None
repl_dict = repl_dict.copy()
repl_dict[self] = expr
return repl_dict
@property
def is_number(self):
return False
[docs]class Derivative(Expr):
"""
Carries out differentiation of the given expression with respect to symbols.
expr must define ._eval_derivative(symbol) method that returns
the differentiation result. This function only needs to consider the
non-trivial case where expr contains symbol and it should call the diff()
method internally (not _eval_derivative); Derivative should be the only
one to call _eval_derivative.
If evaluate is set to True and the expression can not be evaluated, the
list of differentiation symbols will be sorted, that is, the expression is
assumed to have continuous derivatives up to the order asked.
Examples:
* Derivative(Derivative(expr, x), y) -> Derivative(expr, x, y)
* Derivative(expr, x, 3) -> Derivative(expr, x, x, x)
* Derivative(f(x, y), y, x, evaluate=True) -> Derivative(f(x, y), x, y)
"""
is_Derivative = True
def __new__(cls, expr, *symbols, **assumptions):
expr = sympify(expr)
if not symbols:
symbols = expr.free_symbols
if len(symbols) != 1:
raise ValueError("specify differentiation variables to differentiate %s" % expr)
# standardize symbols
symbols = list(sympify(symbols))
if not symbols[-1].is_Integer or len(symbols) == 1:
symbols.append(S.One)
symbol_count = []
all_zero = True
i = 0
while i < len(symbols) - 1: # process up to final Integer
s, count = symbols[i: i+2]
iwas = i
if s.is_Symbol:
if count.is_Symbol:
count = 1
i += 1
elif count.is_Integer:
count = int(count)
i += 2
if i == iwas: # didn't get an update because of bad input
raise ValueError('Derivative expects Symbol [, Integer] args but got %s, %s' % (s, count))
symbol_count.append((s, count))
if all_zero and not count == 0:
all_zero = False
# We make a special case for 0th derivative, because there
# is no good way to unambiguously print this.
if all_zero:
return expr
evaluate = assumptions.pop('evaluate', False)
# look for a quick exit if there are symbols that are not in the free symbols
if evaluate:
if set(sc[0] for sc in symbol_count
).difference(expr.free_symbols):
return S.Zero
# We make a generator so as to only generate a symbol when necessary.
# If a high order of derivative is requested and the expr becomes 0
# after a few differentiations, then we won't need the other symbols
symbolgen = (s for s, count in symbol_count for i in xrange(count))
if expr.is_commutative:
assumptions['commutative'] = True
if (not (hasattr(expr, '_eval_derivative') and
evaluate) and
not isinstance(expr, Derivative)):
symbols = list(symbolgen)
if evaluate:
#TODO: check if assumption of discontinuous derivatives exist
symbols.sort(key=default_sort_key)
obj = Expr.__new__(cls, expr, *symbols, **assumptions)
return obj
# compute the derivative now
unevaluated_symbols = []
for s in symbolgen:
obj = expr._eval_derivative(s)
if obj is None:
unevaluated_symbols.append(s)
elif obj is S.Zero:
return S.Zero
else:
expr = obj
if not unevaluated_symbols:
if isinstance(expr, Derivative):
return Derivative(expr.args[0], *sorted(expr.args[1:],
key=default_sort_key))
return expr
unevaluated_symbols.sort(key=default_sort_key)
return Expr.__new__(cls, expr, *unevaluated_symbols, **assumptions)
def _eval_derivative(self, s):
if s not in self.variables:
obj = self.expr.diff(s)
if not obj:
return obj
if isinstance(obj, Derivative):
return Derivative(obj.expr, *(self.variables + obj.variables))
return Derivative(obj, *self.variables)
return Derivative(self.expr, *(self.variables + (s, )), **{'evaluate': False})
def doit(self, **hints):
expr = self.expr
if hints.get('deep', True):
expr = expr.doit(**hints)
hints['evaluate'] = True
return Derivative(expr, *self.variables, **hints)
@_sympifyit('z0', NotImplementedError)
[docs] def doit_numerically(self, z0):
"""
Evaluate the derivative at z numerically.
When we can represent derivatives at a point, this should be folded
into the normal evalf. For now, we need a special method.
"""
from sympy import mpmath
from sympy.core.expr import Expr
if len(self.free_symbols) != 1 or len(self.variables) != 1:
raise NotImplementedError('partials and higher order derivatives')
z = list(self.free_symbols)[0]
def eval(x):
f0 = self.expr.subs(z, Expr._from_mpmath(x, prec=mpmath.mp.prec))
f0 = f0.evalf(mlib.libmpf.prec_to_dps(mpmath.mp.prec))
return f0._to_mpmath(mpmath.mp.prec)
return Expr._from_mpmath(mpmath.diff(eval, z0._to_mpmath(mpmath.mp.prec)),
mpmath.mp.prec)
@property
def expr(self):
return self._args[0]
@property
def variables(self):
return self._args[1:]
@property
def free_symbols(self):
return self.expr.free_symbols
def _eval_subs(self, old, new):
if self==old:
return new
if old in self.variables and not new.is_Symbol:
# Issue 1620
return Subs(self, old, new)
return Derivative(*map(lambda x: x._eval_subs(old, new), self.args))
def matches(self, expr, repl_dict={}, evaluate=False):
if self in repl_dict:
if repl_dict[self] == expr:
return repl_dict
elif isinstance(expr, Derivative):
if len(expr.variables) == len(self.variables):
return Expr.matches(self, expr, repl_dict, evaluate)
def _eval_lseries(self, x):
dx = self.args[1:]
for term in self.args[0].lseries(x):
yield Derivative(term, *dx)
def _eval_nseries(self, x, n, logx):
arg = self.args[0].nseries(x, n=n, logx=logx)
o = arg.getO()
dx = self.args[1:]
rv = [Derivative(a, *dx) for a in Add.make_args(arg.removeO())]
if o:
rv.append(o/x)
return Add(*rv)
def _eval_as_leading_term(self, x):
return self.args[0].as_leading_term(x)
[docs]class Lambda(Expr):
"""
Lambda(x, expr) represents a lambda function similar to Python's
'lambda x: expr'. A function of several variables is written as
Lambda((x, y, ...), expr).
A simple example:
>>> from sympy import Lambda
>>> from sympy.abc import x
>>> f = Lambda(x, x**2)
>>> f(4)
16
For multivariate functions, use:
>>> from sympy.abc import y, z, t
>>> f2 = Lambda((x, y, z, t), x + y**z + t**z)
>>> f2(1, 2, 3, 4)
73
A handy shortcut for lots of arguments:
>>> p = x, y, z
>>> f = Lambda(p, x + y*z)
>>> f(*p)
x + y*z
"""
is_Function = True
__slots__ = []
def __new__(cls, variables, expr):
try:
variables = Tuple(*variables)
except TypeError:
variables = Tuple(variables)
if len(variables) == 1 and variables[0] == expr:
return S.IdentityFunction
#use dummy variables internally, just to be sure
new_variables = [C.Dummy(arg.name) for arg in variables]
expr = sympify(expr).subs(tuple(zip(variables, new_variables)))
obj = Expr.__new__(cls, Tuple(*new_variables), expr)
return obj
@property
[docs] def variables(self):
"""The variables used in the internal representation of the function"""
return self._args[0]
@property
[docs] def expr(self):
"""The return value of the function"""
return self._args[1]
@property
def free_symbols(self):
return self.expr.free_symbols - set(self.variables)
@property
[docs] def nargs(self):
"""The number of arguments that this function takes"""
return len(self._args[0])
@deprecated
def apply(self, *args): # pragma: no cover
return self(*args)
def __call__(self, *args):
if len(args) != self.nargs:
raise TypeError("%s takes %d arguments (%d given)" % (self, self.nargs, len(args)))
return self.expr.subs(tuple(zip(self.variables, args)))
def __eq__(self, other):
if not isinstance(other, Lambda):
return False
if self.nargs != other.nargs:
return False
selfexpr = self.args[1]
otherexpr = other.args[1]
otherexpr = otherexpr.subs(tuple(zip(other.args[0], self.args[0])))
return selfexpr == otherexpr
def __ne__(self, other):
return not(self == other)
def __hash__(self):
return super(Lambda, self).__hash__()
def _hashable_content(self):
return (self.nargs, ) + tuple(sorted(self.free_symbols))
@property
[docs] def is_identity(self):
"""Return ``True`` if this ``Lambda`` is an identity function. """
if len(self.args) == 2:
return self.args[0] == self.args[1]
else:
return None
[docs]class Subs(Expr):
"""
Represents unevaluated substitutions of an expression.
``Subs(expr, x, x0)`` receives 3 arguments: an expression, a variable or list
of distinct variables and a point or list of evaluation points
corresponding to those variables.
``Subs`` objects are generally useful to represent unevaluated derivatives
calculated at a point.
The variables may be expressions, but they are subjected to the limitations
of subs(), so it is usually a good practice to use only symbols for
variables, since in that case there can be no ambiguity.
There's no automatic expansion - use the method .doit() to effect all
possible substitutions of the object and also of objects inside the
expression.
When evaluating derivatives at a point that is not a symbol, a Subs object
is returned. One is also able to calculate derivatives of Subs objects - in
this case the expression is always expanded (for the unevaluated form, use
Derivative()).
A simple example:
>>> from sympy import Subs, Function, sin
>>> from sympy.abc import x, y, z
>>> f = Function('f')
>>> e = Subs(f(x).diff(x), x, y)
>>> e.subs(y, 0)
Subs(Derivative(f(_x), _x), (_x,), (0,))
>>> e.subs(f, sin).doit()
cos(y)
An example with several variables:
>>> Subs(f(x)*sin(y)+z, (x, y), (0, 1))
Subs(z + f(_x)*sin(_y), (_x, _y), (0, 1))
>>> _.doit()
z + f(0)*sin(1)
"""
def __new__(cls, expr, variables, point, **assumptions):
if not is_sequence(variables, Tuple):
variables = [variables]
variables = Tuple(*sympify(variables))
if uniq(variables) != variables:
repeated = repeated = [ v for v in set(variables)
if list(variables).count(v) > 1 ]
raise ValueError('cannot substitute expressions %s more than '
'once.' % repeated)
if not is_sequence(point, Tuple):
point = [point]
point = Tuple(*sympify(point))
if len(point) != len(variables):
raise ValueError('Number of point values must be the same as '
'the number of variables.')
# it's necessary to use dummy variables internally
new_variables = Tuple(*[ arg.as_dummy() if arg.is_Symbol else
C.Dummy(str(arg)) for arg in variables ])
expr = sympify(expr).subs(tuple(zip(variables, new_variables)))
if expr.is_commutative:
assumptions['commutative'] = True
obj = Expr.__new__(cls, expr, new_variables, point, **assumptions)
return obj
def doit(self):
return self.expr.doit().subs(zip(self.variables, self.point))
def evalf(self):
return self.doit().evalf()
@property
[docs] def variables(self):
"""The variables to be evaluated"""
return self._args[1]
@property
[docs] def expr(self):
"""The expression on which the substitution operates"""
return self._args[0]
@property
[docs] def point(self):
"""The values for which the variables are to be substituted"""
return self._args[2]
@property
def free_symbols(self):
return (self.expr.free_symbols - set(self.variables) |
set(self.point.free_symbols))
def __eq__(self, other):
if not isinstance(other, Subs):
return False
if (len(self.point) != len(other.point) or
self.free_symbols != other.free_symbols or
sorted(self.point) != sorted(other.point)):
return False
# non-repeated point args
selfargs = [ v[0] for v in sorted(zip(self.variables, self.point),
key = lambda v: v[1]) if list(self.point.args).count(v[1]) == 1 ]
otherargs = [ v[0] for v in sorted(zip(other.variables, other.point),
key = lambda v: v[1]) if list(other.point.args).count(v[1]) == 1 ]
# find repeated point values and subs each associated variable
# for a single symbol
selfrepargs = []
otherrepargs = []
if uniq(self.point) != self.point:
repeated = uniq([ v for v in self.point if
list(self.point.args).count(v) > 1 ])
repswap = dict(zip(repeated, [ C.Dummy() for _ in
xrange(len(repeated)) ]))
selfrepargs = [ (self.variables[i], repswap[v]) for i, v in
enumerate(self.point) if v in repeated ]
otherrepargs = [ (other.variables[i], repswap[v]) for i, v in
enumerate(other.point) if v in repeated ]
return self.expr.subs(selfrepargs) == other.expr.subs(
tuple(zip(otherargs, selfargs))).subs(otherrepargs)
def __ne__(self, other):
return not(self == other)
def __hash__(self):
return super(Subs, self).__hash__()
def _hashable_content(self):
return tuple(sorted(self.point)) + tuple(sorted(self.free_symbols))
def _eval_subs(self, old, new):
if self == old:
return new
return Subs(self.expr.subs(old, new), self.variables,
self.point.subs(old, new))
def _eval_derivative(self, s):
if s not in self.free_symbols:
return S.Zero
return Subs(self.expr.diff(s), self.variables, self.point).doit() \
+ Add(*[ Subs(point.diff(s) * self.expr.diff(arg),
self.variables, self.point).doit() for arg,
point in zip(self.variables, self.point) ])
[docs]def diff(f, *symbols, **kwargs):
"""
Differentiate f with respect to symbols.
This is just a wrapper to unify .diff() and the Derivative class; its
interface is similar to that of integrate(). You can use the same
shortcuts for multiple variables as with Derivative. For example,
diff(f(x), x, x, x) and diff(f(x), x, 3) both return the third derivative
of f(x).
You can pass evaluate=False to get an unevaluated Derivative class. Note
that if there are 0 symbols (such as diff(f(x), x, 0), then the result will
be the function (the zeroth derivative), even if evaluate=False.
**Examples**
>>> from sympy import sin, cos, Function, diff
>>> from sympy.abc import x, y
>>> f = Function('f')
>>> diff(sin(x), x)
cos(x)
>>> diff(f(x), x, x, x)
Derivative(f(x), x, x, x)
>>> diff(f(x), x, 3)
Derivative(f(x), x, x, x)
>>> diff(sin(x)*cos(y), x, 2, y, 2)
sin(x)*cos(y)
>>> type(diff(sin(x), x))
cos
>>> type(diff(sin(x), x, evaluate=False))
<class 'sympy.core.function.Derivative'>
>>> type(diff(sin(x), x, 0))
sin
>>> type(diff(sin(x), x, 0, evaluate=False))
sin
>>> diff(sin(x))
cos(x)
>>> diff(sin(x*y))
Traceback (most recent call last):
...
ValueError: specify differentiation variables to differentiate sin(x*y)
Note that ``diff(sin(x))`` syntax is meant only for convenience
in interactive sessions and should be avoided in library code.
See Also
http://documents.wolfram.com/v5/Built-inFunctions/AlgebraicComputation/Calculus/D.html
"""
kwargs.setdefault('evaluate', True)
return Derivative(f, *symbols, **kwargs)
[docs]def expand(e, deep=True, modulus=None, power_base=True, power_exp=True, \
mul=True, log=True, multinomial=True, basic=True, **hints):
"""
Expand an expression using methods given as hints.
Hints are applied with arbitrary order so your code shouldn't
depend on the way hints are passed to this method.
Hints evaluated unless explicitly set to False are:
basic, log, multinomial, mul, power_base, and power_exp
The following hints are supported but not applied unless set to True:
complex, func, and trig.
basic is a generic keyword for methods that want to be expanded
automatically. For example, Integral uses expand_basic to expand the
integrand. If you want your class expand methods to run automatically and
they don't fit one of the already automatic methods, wrap it around
_eval_expand_basic.
If deep is set to True, things like arguments of functions are
recursively expanded. Use deep=False to only expand on the top
level.
If the 'force' hint is used, assumptions about variables will be ignored
in making the expansion.
Also see expand_log, expand_mul, separate, expand_complex, expand_trig,
and expand_func, which are wrappers around those expansion methods.
>>> from sympy import cos, exp
>>> from sympy.abc import x, y, z
mul - Distributes multiplication over addition:
>>> (y*(x + z)).expand(mul=True)
x*y + y*z
complex - Split an expression into real and imaginary parts:
>>> (x + y).expand(complex=True)
I*im(x) + I*im(y) + re(x) + re(y)
>>> cos(x).expand(complex=True)
-I*sin(re(x))*sinh(im(x)) + cos(re(x))*cosh(im(x))
power_exp - Expand addition in exponents into multiplied bases:
>>> exp(x + y).expand(power_exp=True)
exp(x)*exp(y)
>>> (2**(x + y)).expand(power_exp=True)
2**x*2**y
power_base - Split powers of multiplied bases if assumptions allow
or if the 'force' hint is used:
>>> ((x*y)**z).expand(power_base=True)
(x*y)**z
>>> ((x*y)**z).expand(power_base=True, force=True)
x**z*y**z
>>> ((2*y)**z).expand(power_base=True)
2**z*y**z
log - Pull out power of an argument as a coefficient and split logs products
into sums of logs. Note that these only work if the arguments of the log
function have the proper assumptions: the arguments must be positive and the
exponents must be real or else the force hint must be True:
>>> from sympy import log, symbols, oo
>>> log(x**2*y).expand(log=True)
log(x**2*y)
>>> log(x**2*y).expand(log=True, force=True)
2*log(x) + log(y)
>>> x, y = symbols('x,y', positive=True)
>>> log(x**2*y).expand(log=True)
2*log(x) + log(y)
trig - Do trigonometric expansions:
>>> cos(x + y).expand(trig=True)
-sin(x)*sin(y) + cos(x)*cos(y)
func - Expand other functions:
>>> from sympy import gamma
>>> gamma(x + 1).expand(func=True)
x*gamma(x)
multinomial - Expand (x + y + ...)**n where n is a positive integer:
>>> ((x + y + z)**2).expand(multinomial=True)
x**2 + 2*x*y + 2*x*z + y**2 + 2*y*z + z**2
You can shut off methods that you don't want:
>>> (exp(x + y)*(x + y)).expand()
x*exp(x)*exp(y) + y*exp(x)*exp(y)
>>> (exp(x + y)*(x + y)).expand(power_exp=False)
x*exp(x + y) + y*exp(x + y)
>>> (exp(x + y)*(x + y)).expand(mul=False)
(x + y)*exp(x)*exp(y)
Use deep=False to only expand on the top level:
>>> exp(x + exp(x + y)).expand()
exp(x)*exp(exp(x)*exp(y))
>>> exp(x + exp(x + y)).expand(deep=False)
exp(x)*exp(exp(x + y))
Note: because hints are applied in arbitrary order, some hints may
prevent expansion by other hints if they are applied first. In
particular, mul may distribute multiplications and prevent log and
power_base from expanding them. Also, if mul is applied before multinomial,
the expression might not be fully distributed. The solution is to expand
with mul=False first, then run expand_mul if you need further expansion.
Examples:
>>> from sympy import expand_log, expand, expand_mul
>>> x, y, z = symbols('x,y,z', positive=True)
::
expand(log(x*(y + z))) # could be either one below
log(x*y + x*z)
log(x) + log(y + z)
>>> expand_log(log(x*y + x*z))
log(x*y + x*z)
>>> expand(log(x*(y + z)), mul=False)
log(x) + log(y + z)
::
expand((x*(y + z))**x) # could be either one below
(x*y + x*z)**x
x**x*(y + z)**x
>>> expand((x*(y + z))**x, mul=False)
x**x*(y + z)**x
::
expand(x*(y + z)**2) # could be either one below
2*x*y*z + x*y**2 + x*z**2
x*(y + z)**2
>>> expand(x*(y + z)**2, mul=False)
x*(y**2 + 2*y*z + z**2)
>>> expand_mul(_)
x*y**2 + 2*x*y*z + x*z**2
"""
hints['power_base'] = power_base
hints['power_exp'] = power_exp
hints['mul'] = mul
hints['log'] = log
hints['multinomial'] = multinomial
hints['basic'] = basic
return sympify(e).expand(deep=deep, modulus=modulus, **hints)
# These are simple wrappers around single hints. Feel free to add ones for
# power_exp, power_base, multinomial, or basic if you need them.
[docs]def expand_mul(expr, deep=True):
"""
Wrapper around expand that only uses the mul hint. See the expand
docstring for more information.
Example:
>>> from sympy import symbols, expand_mul, exp, log
>>> x, y = symbols('x,y', positive=True)
>>> expand_mul(exp(x+y)*(x+y)*log(x*y**2))
x*exp(x + y)*log(x*y**2) + y*exp(x + y)*log(x*y**2)
"""
return sympify(expr).expand(deep=deep, mul=True, power_exp=False,\
power_base=False, basic=False, multinomial=False, log=False)
[docs]def expand_multinomial(expr, deep=True):
"""
Wrapper around expand that only uses the multinomial hint. See the expand
docstring for more information.
Example:
>>> from sympy import symbols, expand_multinomial, exp
>>> x, y = symbols('x y', positive=True)
>>> expand_multinomial((x + exp(x + 1))**2)
x**2 + 2*x*exp(x + 1) + exp(2*x + 2)
"""
return sympify(expr).expand(deep=deep, mul=False, power_exp=False,\
power_base=False, basic=False, multinomial=True, log=False)
[docs]def expand_log(expr, deep=True):
"""
Wrapper around expand that only uses the log hint. See the expand
docstring for more information.
Example:
>>> from sympy import symbols, expand_log, exp, log
>>> x, y = symbols('x,y', positive=True)
>>> expand_log(exp(x+y)*(x+y)*log(x*y**2))
(x + y)*(log(x) + 2*log(y))*exp(x + y)
"""
return sympify(expr).expand(deep=deep, log=True, mul=False,\
power_exp=False, power_base=False, multinomial=False, basic=False)
[docs]def expand_func(expr, deep=True):
"""
Wrapper around expand that only uses the func hint. See the expand
docstring for more information.
Example:
>>> from sympy import expand_func, gamma
>>> from sympy.abc import x
>>> expand_func(gamma(x + 2))
x*(x + 1)*gamma(x)
"""
return sympify(expr).expand(deep=deep, func=True, basic=False,\
log=False, mul=False, power_exp=False, power_base=False, multinomial=False)
[docs]def expand_trig(expr, deep=True):
"""
Wrapper around expand that only uses the trig hint. See the expand
docstring for more information.
Example:
>>> from sympy import expand_trig, sin, cos
>>> from sympy.abc import x, y
>>> expand_trig(sin(x+y)*(x+y))
(x + y)*(sin(x)*cos(y) + sin(y)*cos(x))
"""
return sympify(expr).expand(deep=deep, trig=True, basic=False,\
log=False, mul=False, power_exp=False, power_base=False, multinomial=False)
[docs]def expand_complex(expr, deep=True):
"""
Wrapper around expand that only uses the complex hint. See the expand
docstring for more information.
Example:
>>> from sympy import expand_complex, I, im, re
>>> from sympy.abc import z
>>> expand_complex(z**(2*I))
I*im(z**(2*I)) + re(z**(2*I))
"""
return sympify(expr).expand(deep=deep, complex=True, basic=False,\
log=False, mul=False, power_exp=False, power_base=False, multinomial=False)
[docs]def count_ops(expr, visual=False):
"""
Return a representation (integer or expression) of the operations in expr.
If ``visual`` is ``False`` (default) then the sum of the coefficients of the
visual expression will be returned.
If ``visual`` is ``True`` then the number of each type of operation is shown
with the core class types (or their virtual equivalent) multiplied by the
number of times they occur.
If expr is an iterable, the sum of the op counts of the
items will be returned.
Examples:
>>> from sympy.abc import a, b, x, y
>>> from sympy import sin, count_ops
Although there isn't a SUB object, minus signs are interpreted as
either negations or subtractions:
>>> (x - y).count_ops(visual=True)
SUB
>>> (-x).count_ops(visual=True)
NEG
Here, there are two Adds and a Pow:
>>> (1 + a + b**2).count_ops(visual=True)
2*ADD + POW
In the following, an Add, Mul, Pow and two functions:
>>> (sin(x)*x + sin(x)**2).count_ops(visual=True)
ADD + MUL + POW + 2*SIN
for a total of 5:
>>> (sin(x)*x + sin(x)**2).count_ops(visual=False)
5
Note that "what you type" is not always what you get. The expression
1/x/y is translated by sympy into 1/(x*y) so it gives a DIV and MUL rather
than two DIVs:
>>> (1/x/y).count_ops(visual=True)
DIV + MUL
The visual option can be used to demonstrate the difference in
operations for expressions in different forms. Here, the Horner
representation is compared with the expanded form of a polynomial:
>>> eq=x*(1 + x*(2 + x*(3 + x)))
>>> count_ops(eq.expand(), visual=True) - count_ops(eq, visual=True)
-MUL + 3*POW
The count_ops function also handles iterables:
>>> count_ops([x, sin(x), None, True, x + 2], visual=False)
2
>>> count_ops([x, sin(x), None, True, x + 2], visual=True)
ADD + SIN
>>> count_ops({x: sin(x), x + 2: y + 1}, visual=True)
2*ADD + SIN
"""
from sympy.simplify.simplify import fraction
expr = sympify(expr)
if isinstance(expr, Expr):
ops = []
args = [expr]
NEG = C.Symbol('NEG')
DIV = C.Symbol('DIV')
SUB = C.Symbol('SUB')
ADD = C.Symbol('ADD')
def isneg(a):
c = a.as_coeff_mul()[0]
return c.is_Number and c.is_negative
while args:
a = args.pop()
if a.is_Rational:
#-1/3 = NEG + DIV
if a is not S.One:
if a.p < 0:
ops.append(NEG)
if a.q != 1:
ops.append(DIV)
continue
elif a.is_Mul:
if isneg(a):
ops.append(NEG)
if a.args[0] is S.NegativeOne:
a = a.as_two_terms()[1]
else:
a = -a
n, d = fraction(a)
if n.is_Integer:
ops.append(DIV)
if n < 0:
ops.append(NEG)
args.append(d)
continue # won't be -Mul but could be Add
elif d is not S.One:
if not d.is_Integer:
args.append(d)
ops.append(DIV)
args.append(n)
continue # could be -Mul
elif a.is_Add:
aargs = list(a.args)
negs = 0
for i, ai in enumerate(aargs):
if isneg(ai):
negs += 1
args.append(-ai)
if i > 0:
ops.append(SUB)
else:
args.append(ai)
if i > 0:
ops.append(ADD)
if negs == len(aargs): # -x - y = NEG + SUB
ops.append(NEG)
elif isneg(aargs[0]): # -x + y = SUB, but we already recorded an ADD
ops.append(SUB - ADD)
continue
if a.is_Pow and a.exp is S.NegativeOne:
ops.append(DIV)
args.append(a.base) # won't be -Mul but could be Add
continue
if (a.is_Mul or
a.is_Pow or
a.is_Function or
isinstance(a, Derivative) or
isinstance(a, C.Integral)):
o = C.Symbol(a.func.__name__.upper())
# count the args
if (a.is_Mul or
isinstance(a, C.LatticeOp)):
ops.append(o*(len(a.args) - 1))
else:
ops.append(o)
args.extend(a.args)
elif type(expr) is dict:
ops = [count_ops(k, visual=visual) +
count_ops(v, visual=visual) for k, v in expr.iteritems()]
elif iterable(expr):
ops = [count_ops(i, visual=visual) for i in expr]
elif not isinstance(expr, Basic):
ops = []
else: # it's Basic not isinstance(expr, Expr):
assert isinstance(expr, Basic)
ops = [count_ops(a, visual=visual) for a in expr.args]
if not ops:
if visual:
return S.Zero
return 0
ops = Add(*ops)
if visual:
return ops
if ops.is_Number:
return int(ops)
return sum(int((a.args or [1])[0]) for a in Add.make_args(ops))
from sympify import sympify
from add import Add