docstring for Function.canonize() added
[sympy.git] / sympy / core / function.py
blob0bc147d2ef2a92307cc9c782324108258b9d0d8e
1 """
2 NOTE: this doctest is for now obsoleted interface.
4 There are different types of functions:
5 1) defined function like exp or sin that has a name and body
6 (in the sense that function can be evaluated).
7 e = exp
8 2) undefined function with a name but no body. Undefined
9 functions can be defined using Symbol class as follows:
10 f = Symbol('f', function=True)
11 (the result will be Function instance)
13 f = Function('f')
14 3) anonymous function or lambda function that has no name
15 but has body with dummy variables. An anonymous function
16 object creation examples:
17 f = Lambda(x, exp(x)*x)
18 f = Lambda(exp(x)*x) # free symbols in the expression define the number of arguments
19 f = exp * Lambda(x,x)
20 4) composition of functions, inverse functions
22 One can perform certain operations with functions, the
23 result will be a lambda function. Allowed operations are
24 addition and multiplication. Function multiplication is
25 elementise ie (f*g)(x) is equivalent to f(x) * g(x).
26 Multiplication by a number is equivalent to multiplication
27 by a constant function.
28 Composition of functions is achived via Composition class@
31 f+Composition(2,exp,sin) -> lambda _x: f(x)+2*exp(sin(x))
33 In the above functions did not have arguments, then
34 it is said that functions are in unevaluated form.
35 When calling a function with arguments, then we get
36 an instance of the function value. Eg
38 exp(1) -> 1
39 (2*f)(x) -> 2 * f(x)
40 Lambda(x, exp(x)*x)(y) -> exp(y)*y
42 One can construct undefined function values from Symbol
43 object:
44 f = Symbol('f')
45 fx = f(x)
46 fx.func -> Function('f')
47 fx[:] -> (Symbol('x'),)
48 As seen above, function values are Apply instances and
49 have attributes .func and [:].
50 """
52 from basic import Basic, Singleton, Atom, cache_it, S
53 from basic_methods import BasicType, MetaBasicMeths
54 from methods import ArithMeths, NoRelMeths, RelMeths
55 from operations import AssocOp
57 class FunctionClass(MetaBasicMeths):
58 """
59 Base class for function classes. FunctionClass is a subclass of type.
61 Use Function('<function name>' [ , signature ]) to create
62 undefined function classes.
63 """
65 _new = type.__new__
67 def __new__(cls, arg1, arg2, arg3=None, **options):
68 assert not options,`options`
69 if isinstance(arg1, type):
70 ftype, name, signature = arg1, arg2, arg3
71 #XXX this probably needs some fixing:
72 assert ftype.__name__.endswith('Function'),`ftype`
73 attrdict = ftype.__dict__.copy()
74 attrdict['undefined_Function'] = True
75 if signature is not None:
76 attrdict['signature'] = signature
77 bases = (ftype,)
78 return type.__new__(cls, name, bases, attrdict)
79 else:
80 name, bases, attrdict = arg1, arg2, arg3
81 return type.__new__(cls, name, bases, attrdict)
83 def torepr(cls):
84 return cls.__name__
86 class Function(Basic, RelMeths):
87 """
88 Base class for applied functions.
89 Constructor of undefined classes.
91 """
93 __metaclass__ = FunctionClass
95 precedence = Basic.Apply_precedence
97 nofargs = None
99 @cache_it
100 def __new__(cls, *args, **options):
101 if cls is SingleValuedFunction or cls is Function:
102 #when user writes SingleValuedFunction("f"), do an equivalent of:
103 #taking the whole class SingleValuedFunction(...):
104 #and rename the SingleValuedFunction to "f" and return f, thus:
105 #In [13]: isinstance(f, SingleValuedFunction)
106 #Out[13]: False
107 #In [14]: isinstance(f, FunctionClass)
108 #Out[14]: True
110 if len(args) == 1 and isinstance(args[0], str):
111 #always create SingleValuedFunction
112 return FunctionClass(SingleValuedFunction, *args)
113 return FunctionClass(SingleValuedFunction, *args, **options)
114 else:
115 print args
116 print type(args[0])
117 raise Exception("You need to specify exactly one string")
118 args = map(Basic.sympify, args)
119 # these lines should be refactored
120 if "nofargs" in options:
121 del options["nofargs"]
122 if "dummy" in options:
123 del options["dummy"]
124 if "comparable" in options:
125 del options["comparable"]
126 if "noncommutative" in options:
127 del options["noncommutative"]
128 if "commutative" in options:
129 del options["commutative"]
130 # up to here.
131 r = cls.canonize(*args, **options)
132 if isinstance(r, Basic):
133 return r
134 elif r is None:
135 pass
136 elif not isinstance(r, tuple):
137 args = (r,)
138 return Basic.__new__(cls, *args, **options)
140 @property
141 def is_comparable(self):
142 return True
144 @property
145 def is_commutative(self):
146 return True
148 @classmethod
149 def canonize(cls, *args, **options):
151 Returns a canonical form of cls applied to arguments args.
153 The canonize() method is called when the class cls is about to be
154 instantiated and it should return either some simplified instance
155 (possible of some other class), or if the class cls should be
156 unmodified, return None.
158 Example of canonize() for the function "sign"
159 ---------------------------------------------
161 @classmethod
162 def canonize(cls, arg):
163 if isinstance(arg, Basic.NaN):
164 return S.NaN
165 if isinstance(arg, Basic.Zero): return S.One
166 if arg.is_positive: return S.One
167 if arg.is_negative: return S.NegativeOne
168 if isinstance(arg, Basic.Mul):
169 coeff, terms = arg.as_coeff_terms()
170 if not isinstance(coeff, Basic.One):
171 return cls(coeff) * cls(Basic.Mul(*terms))
174 return
176 @property
177 def func(self):
178 return self.__class__
180 def _eval_subs(self, old, new):
181 if self == old:
182 return new
183 elif isinstance(old, FunctionClass) and isinstance(new, FunctionClass):
184 if old == self.func and old.nofargs == new.nofargs:
185 return new(*self[:])
186 obj = self.func._eval_apply_subs(*(self[:] + (old,) + (new,)))
187 if obj is not None:
188 return obj
189 return Basic._seq_subs(self, old, new)
191 def _eval_expand_basic(self, *args):
192 return self
194 def _eval_evalf(self):
195 obj = self.func._eval_apply_evalf(*self[:])
196 if obj is None:
197 return self
198 return obj
200 def _eval_is_comparable(self):
201 if isinstance(self.func, DefinedFunction):
202 r = True
203 for s in self:
204 c = s.is_comparable
205 if c is None: return
206 if not c: r = False
207 return r
208 return
210 def _eval_derivative(self, s):
211 # Apply(f(x), x).diff(s) -> x.diff(s) * f.fdiff(1)(s)
212 i = 0
213 l = []
214 r = Basic.Zero()
215 for a in self:
216 i += 1
217 da = a.diff(s)
218 if isinstance(da, Basic.Zero):
219 continue
220 if isinstance(self.func, FunctionClass):
221 df = self.fdiff(i)
222 l.append(df * da)
223 #else:
224 # df = self.func.fdiff(i)
225 # l.append(Apply(df,*self[:]) * da)
226 return Basic.Add(*l)
228 def _eval_power(b, e):
229 if len(b[:])==1:
230 return b.func._eval_apply_power(b[0], e)
231 return
233 def _eval_is_commutative(self):
234 r = True
235 for a in self._args:
236 c = a.is_commutative
237 if c is None: return None
238 if not c: r = False
239 return r
241 def _calc_positive(self):
242 return self.func._calc_apply_positive(*self[:])
244 def _calc_real(self):
245 return self.func._calc_apply_real(*self[:])
247 def _calc_unbounded(self):
248 return self.func._calc_apply_unbounded(*self[:])
250 def _eval_eq_nonzero(self, other):
251 if isinstance(other.func, self.func.__class__) and len(self)==len(other):
252 for a1,a2 in zip(self,other):
253 if not (a1==a2):
254 return False
255 return True
257 def as_base_exp(self):
258 return self, Basic.One()
260 def count_ops(self, symbolic=True):
261 # f() args
262 return 1 + Basic.Add(*[t.count_ops(symbolic) for t in self])
264 def _eval_oseries(self, order):
265 assert self.func.nofargs==1,`self.func`
266 arg = self[0]
267 x = order.symbols[0]
268 if not Basic.Order(1,x).contains(arg):
269 return self.func(arg)
270 arg0 = arg.limit(x, 0)
271 if not isinstance(arg0, Basic.Zero):
272 e = self.func(arg)
273 e1 = e.expand()
274 if e==e1:
275 #one example is e = sin(x+1)
276 #let's try the general algorithm
277 term = e.subs(x, Basic.Rational(0))
278 series = Basic.Rational(0)
279 fact = Basic.Rational(1)
280 i = 0
281 while not order.contains(term):
282 series += term
283 i += 1
284 fact *= Basic.Rational(i)
285 e = e.diff(x)
286 term = e.subs(x, Basic.Rational(0))*(x**i)/fact
287 return series
289 #print '%s(%s).oseries(%s) is unevaluated' % (self.func,arg,order)
290 return e1.oseries(order)
291 return self._compute_oseries(arg, order, self.func.taylor_term, self.func)
293 def _eval_is_polynomial(self, syms):
294 for arg in self:
295 if arg.has(*syms):
296 return False
297 return True
299 def _eval_expand_complex(self, *args):
300 func = self.func(*[ a._eval_expand_complex(*args) for a in self ])
301 return Basic.Re()(func) + S.ImaginaryUnit * Basic.Im()(func)
303 def _eval_rewrite(self, pattern, rule, **hints):
304 if hints.get('deep', False):
305 args = [ a._eval_rewrite(pattern, rule, **hints) for a in self ]
306 else:
307 args = self[:]
309 if pattern is None or isinstance(self.func, pattern):
310 if hasattr(self, rule):
311 rewritten = getattr(self, rule)(*args)
313 if rewritten is not None:
314 return rewritten
316 return self.func(*args, **self._assumptions)
318 def fdiff(self, argindex=1):
319 if self.nofargs is not None:
320 if isinstance(self.nofargs, tuple):
321 nofargs = self.nofargs[-1]
322 else:
323 nofargs = self.nofargs
324 if not (1<=argindex<=nofargs):
325 raise TypeError("argument index %r is out of range [1,%s]" % (i,nofargs))
326 return Derivative(self,self[argindex-1],evaluate=False)
328 def tostr(self, level=0):
329 p = self.precedence
330 r = '%s(%s)' % (self.func.__name__, ', '.join([a.tostr() for a in self]))
331 if p <= level:
332 return '(%s)' % (r)
333 return r
335 @classmethod
336 def _eval_apply_evalf(cls, arg):
337 return
339 class WildFunction(Function, Atom):
341 nofargs = 1
343 def matches(pattern, expr, repl_dict={}, evaluate=False):
344 for p,v in repl_dict.items():
345 if p==pattern:
346 if v==expr: return repl_dict
347 return None
348 if pattern.nofargs is not None:
349 if pattern.nofargs != expr.nofargs:
350 return None
351 repl_dict = repl_dict.copy()
352 repl_dict[pattern] = expr
353 return repl_dict
355 def tostr(self, level=0):
356 return self.name + '_'
358 @classmethod
359 def _eval_apply_evalf(cls, arg):
360 return
362 class Lambda(Function):
364 Lambda(expr, arg1, arg2, ...) -> lambda arg1, arg2,... : expr
366 Lambda instance has the same assumptions as its body.
369 precedence = Basic.Lambda_precedence
370 name = None
371 has_derivative = True
373 def __new__(cls, expr, *args):
374 expr = Basic.sympify(expr)
375 args = tuple(map(Basic.sympify, args))
376 #if isinstance(expr, Apply):
377 # if expr[:]==args:
378 # return expr.func
379 dummy_args = []
380 for a in args:
381 if not isinstance(a, Basic.Symbol):
382 raise TypeError("%s %s-th argument must be Symbol instance (got %r)" \
383 % (cls.__name__, len(dummy_args)+1,a))
384 d = a.as_dummy()
385 expr = expr.subs(a, d)
386 dummy_args.append(d)
387 obj = Basic.__new__(cls, expr, *dummy_args, **expr._assumptions)
388 return obj
390 def _hashable_content(self):
391 return self._args
393 @property
394 def nofargs(self):
395 return len(self._args)-1
397 def __getitem__(self, iter):
398 return self._args[1:][iter]
400 def __len__(self):
401 return len(self[:])
403 @property
404 def body(self):
405 return self._args[0]
407 def tostr(self, level=0):
408 precedence = self.precedence
409 r = 'lambda %s: %s' % (', '.join([a.tostr() for a in self]),
410 self.body.tostr(precedence))
411 if precedence <= level:
412 return '(%s)' % r
413 return r
415 def torepr(self):
416 return '%s(%s)' % (self.__class__.__name__, ', '.join([a.torepr() for a in self]))
418 def as_coeff_terms(self, x=None):
419 c,t = self.body.as_coeff_terms(x)
420 return c, [Lambda(Basic.Mul(*t),*self[:])]
422 def _eval_power(b, e):
424 (lambda x:f(x))**e -> (lambda x:f(x)**e)
426 return Lambda(b.body**e, *b[:])
428 def _eval_fpower(b, e):
430 FPow(lambda x:f(x), 2) -> lambda x:f(f(x)))
432 if isinstance(e, Basic.Integer) and e.is_positive and e.p < 10 and len(b)==1:
433 r = b.body
434 for i in xrange(e.p-1):
435 r = b(r)
436 return Lambda(r, *b[:])
438 def with_dummy_arguments(self, args = None):
439 if args is None:
440 args = tuple([a.as_dummy() for a in self])
441 if len(args) != len(self):
442 raise TypeError("different number of arguments in Lambda functions: %s, %s" % (len(args), len(self)))
443 expr = self.body
444 for a,na in zip(self, args):
445 expr = expr.subs(a, na)
446 return expr, args
448 def _eval_expand_basic(self, *args):
449 return Lambda(self.body._eval_expand_basic(*args), *self[:])
451 def diff(self, *symbols):
452 return Lambda(self.body.diff(*symbols), *self[:])
454 def fdiff(self, argindex=1):
455 if not (1<=argindex<=len(self)):
456 raise TypeError("%s.fderivative() argindex %r not in the range [1,%s]"\
457 % (self.__class__, argindex, len(self)))
458 s = self[argindex-1]
459 expr = self.body.diff(s)
460 return Lambda(expr, *self[:])
462 _eval_subs = Basic._seq_subs
464 def canonize(cls, *args):
465 n = cls.nofargs
466 if n!=len(args):
467 raise TypeError('%s takes exactly %s arguments (got %s)'\
468 % (cls, n, len(args)))
469 expr = cls.body
470 for da,a in zip(cls, args):
471 expr = expr.subs(da,a)
472 return expr
474 class Derivative(Basic, ArithMeths, RelMeths):
476 Carries out differentation of the given expression with respect to symbols.
478 expr must define ._eval_derivative(symbol) method that returns differentation result or None.
480 Derivative(Derivative(expr, x), y) -> Derivative(expr, x, y)
483 precedence = Basic.Apply_precedence
485 def __new__(cls, expr, *symbols, **assumptions):
486 expr = Basic.sympify(expr)
487 if not symbols: return expr
488 symbols = map(Basic.sympify, symbols)
490 if not assumptions.get("evaluate", True):
491 obj = Basic.__new__(cls, expr, *symbols)
492 return obj
494 for s in symbols:
495 assert isinstance(s, Basic.Symbol),`s`
496 if not expr.has(s):
497 return Basic.Zero()
499 unevaluated_symbols = []
500 for s in symbols:
501 obj = expr._eval_derivative(s)
502 if obj is None:
503 unevaluated_symbols.append(s)
504 else:
505 expr = obj
507 if not unevaluated_symbols:
508 return expr
509 return Basic.__new__(cls, expr, *unevaluated_symbols)
511 def xas_apply(self):
512 # Derivative(f(x),x) -> Apply(Lambda(f(_x),_x), x)
513 symbols = []
514 indices = []
515 for s in self.symbols:
516 if s not in symbols:
517 symbols.append(s)
518 indices.append(len(symbols))
519 else:
520 indices.append(symbols.index(s)+1)
521 stop
522 return Apply(FApply(FDerivative(*indices), Lambda(self.expr, *symbols)), *symbols)
524 def _eval_derivative(self, s):
525 #print
526 #print self
527 #print s
528 #stop
529 if s not in self.symbols:
530 obj = self.expr.diff(s)
531 if isinstance(obj, Derivative):
532 return Derivative(obj.expr, *(obj.symbols+self.symbols))
533 return Derivative(obj, *self.symbols)
534 return Derivative(self.expr, *((s,)+self.symbols), **{'evaluate': False})
536 def doit(self):
537 return Derivative(self.expr, *self.symbols)
539 @property
540 def expr(self):
541 return self._args[0]
543 @property
544 def symbols(self):
545 return self._args[1:]
547 def tostr(self, level=0):
548 r = 'D' + `tuple(self)`
549 if self.precedence <= level:
550 r = '(%s)' % (r)
551 return r
553 def _eval_subs(self, old, new):
554 return Derivative(self[0].subs(old, new), *self[1:])
556 def matches(pattern, expr, repl_dict={}, evaluate=False):
557 # this method needs a cleanup.
559 #print "? :",pattern, expr, repl_dict, evaluate
560 #if repl_dict:
561 # return repl_dict
562 for p,v in repl_dict.items():
563 if p==pattern:
564 if v==expr: return repl_dict
565 return None
566 assert isinstance(pattern, Derivative)
567 if isinstance(expr, Derivative):
568 if len(expr.symbols) == len(pattern.symbols):
569 #print "MAYBE:",pattern, expr, repl_dict, evaluate
570 return Basic.matches(pattern, expr, repl_dict, evaluate)
571 #print "NONE:",pattern, expr, repl_dict, evaluate
572 return None
573 #print pattern, expr, repl_dict, evaluate
574 stop
575 if pattern.nofargs is not None:
576 if pattern.nofargs != expr.nofargs:
577 return None
578 repl_dict = repl_dict.copy()
579 repl_dict[pattern] = expr
580 return repl_dict
583 def diff(f, x, times = 1, evaluate=True):
584 """Differentiate f with respect to x
586 It's just a wrapper to unify .diff() and the Derivative class,
587 it's interface is similar to that of integrate()
589 see http://documents.wolfram.com/v5/Built-inFunctions/AlgebraicComputation/Calculus/D.html
591 f = Basic.sympify(f)
592 if evaluate == True:
593 for i in range(0,times):
594 f = f.diff(x)
595 return f
596 else:
597 return Derivative(f, x, evaluate=evaluate)
601 # TODO rename me to something more appropriate? e.g. ArithFunction (or just
602 # Function?)
603 class SingleValuedFunction(ArithMeths, Function):
605 Single-valued functions.
608 @classmethod
609 def _eval_apply_evalf(cls, arg):
610 arg = arg.evalf()
612 #if cls.nofargs == 1:
613 # common case for functions with 1 argument
614 #if isinstance(arg, Basic.Number):
615 if arg.is_number:
616 func_evalf = getattr(arg, cls.__name__)
617 return func_evalf()