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).
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)
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
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
40 Lambda(x, exp(x)*x)(y) -> exp(y)*y
42 One can construct undefined function values from Symbol
46 fx.func -> Function('f')
47 fx[:] -> (Symbol('x'),)
48 As seen above, function values are Apply instances and
49 have attributes .func and [:].
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
):
59 Base class for function classes. FunctionClass is a subclass of type.
61 Use Function('<function name>' [ , signature ]) to create
62 undefined function classes.
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
78 return type.__new
__(cls
, name
, bases
, attrdict
)
80 name
, bases
, attrdict
= arg1
, arg2
, arg3
81 return type.__new
__(cls
, name
, bases
, attrdict
)
86 class Function(Basic
, RelMeths
):
88 Base class for applied functions.
89 Constructor of undefined classes.
93 __metaclass__
= FunctionClass
95 precedence
= Basic
.Apply_precedence
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)
107 #In [14]: isinstance(f, FunctionClass)
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
)
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
:
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"]
131 r
= cls
.canonize(*args
, **options
)
132 if isinstance(r
, Basic
):
136 elif not isinstance(r
, tuple):
138 return Basic
.__new
__(cls
, *args
, **options
)
141 def is_comparable(self
):
145 def is_commutative(self
):
149 def canonize(cls
, *args
, **options
):
154 return self
.__class
__
156 def _eval_subs(self
, old
, new
):
159 elif isinstance(old
, FunctionClass
) and isinstance(new
, FunctionClass
):
160 if old
== self
.func
and old
.nofargs
== new
.nofargs
:
162 obj
= self
.func
._eval
_apply
_subs
(*(self
[:] + (old
,) + (new
,)))
165 return Basic
._seq
_subs
(self
, old
, new
)
167 def _eval_expand_basic(self
, *args
):
170 def _eval_evalf(self
):
171 obj
= self
.func
._eval
_apply
_evalf
(*self
[:])
176 def _eval_is_comparable(self
):
177 if isinstance(self
.func
, DefinedFunction
):
186 def _eval_derivative(self
, s
):
187 # Apply(f(x), x).diff(s) -> x.diff(s) * f.fdiff(1)(s)
194 if isinstance(da
, Basic
.Zero
):
196 if isinstance(self
.func
, FunctionClass
):
200 # df = self.func.fdiff(i)
201 # l.append(Apply(df,*self[:]) * da)
204 def _eval_power(b
, e
):
206 return b
.func
._eval
_apply
_power
(b
[0], e
)
209 def _eval_is_commutative(self
):
213 if c
is None: return None
217 def _calc_positive(self
):
218 return self
.func
._calc
_apply
_positive
(*self
[:])
220 def _calc_real(self
):
221 return self
.func
._calc
_apply
_real
(*self
[:])
223 def _calc_unbounded(self
):
224 return self
.func
._calc
_apply
_unbounded
(*self
[:])
226 def _eval_eq_nonzero(self
, other
):
227 if isinstance(other
.func
, self
.func
.__class
__) and len(self
)==len(other
):
228 for a1
,a2
in zip(self
,other
):
233 def as_base_exp(self
):
234 return self
, Basic
.One()
236 def count_ops(self
, symbolic
=True):
238 return 1 + Basic
.Add(*[t
.count_ops(symbolic
) for t
in self
])
240 def _eval_oseries(self
, order
):
241 assert self
.func
.nofargs
==1,`self
.func`
244 if not Basic
.Order(1,x
).contains(arg
):
245 return self
.func(arg
)
246 arg0
= arg
.limit(x
, 0)
247 if not isinstance(arg0
, Basic
.Zero
):
251 #one example is e = sin(x+1)
252 #let's try the general algorithm
253 term
= e
.subs(x
, Basic
.Rational(0))
254 series
= Basic
.Rational(0)
255 fact
= Basic
.Rational(1)
257 while not order
.contains(term
):
260 fact
*= Basic
.Rational(i
)
262 term
= e
.subs(x
, Basic
.Rational(0))*(x
**i
)/fact
265 #print '%s(%s).oseries(%s) is unevaluated' % (self.func,arg,order)
266 return e1
.oseries(order
)
267 return self
._compute
_oseries
(arg
, order
, self
.func
.taylor_term
, self
.func
)
269 def _eval_is_polynomial(self
, syms
):
275 def _eval_expand_complex(self
, *args
):
276 func
= self
.func(*[ a
._eval
_expand
_complex
(*args
) for a
in self
])
277 return Basic
.Re()(func
) + S
.ImaginaryUnit
* Basic
.Im()(func
)
279 def _eval_rewrite(self
, pattern
, rule
, **hints
):
280 if hints
.get('deep', False):
281 args
= [ a
._eval
_rewrite
(pattern
, rule
, **hints
) for a
in self
]
285 if pattern
is None or isinstance(self
.func
, pattern
):
286 if hasattr(self
, rule
):
287 rewritten
= getattr(self
, rule
)(*args
)
289 if rewritten
is not None:
292 return self
.func(*args
, **self
._assumptions
)
294 def fdiff(self
, argindex
=1):
295 if self
.nofargs
is not None:
296 if isinstance(self
.nofargs
, tuple):
297 nofargs
= self
.nofargs
[-1]
299 nofargs
= self
.nofargs
300 if not (1<=argindex
<=nofargs
):
301 raise TypeError("argument index %r is out of range [1,%s]" % (i
,nofargs
))
302 return Derivative(self
,self
[argindex
-1],evaluate
=False)
304 def tostr(self
, level
=0):
306 r
= '%s(%s)' % (self
.func
.__name
__, ', '.join([a
.tostr() for a
in self
]))
312 def _eval_apply_evalf(cls
, arg
):
315 class WildFunction(Function
, Atom
):
319 def matches(pattern
, expr
, repl_dict
={}, evaluate
=False):
320 for p
,v
in repl_dict
.items():
322 if v
==expr
: return repl_dict
324 if pattern
.nofargs
is not None:
325 if pattern
.nofargs
!= expr
.nofargs
:
327 repl_dict
= repl_dict
.copy()
328 repl_dict
[pattern
] = expr
331 def tostr(self
, level
=0):
332 return self
.name
+ '_'
335 def _eval_apply_evalf(cls
, arg
):
338 class Lambda(Function
):
340 Lambda(expr, arg1, arg2, ...) -> lambda arg1, arg2,... : expr
342 Lambda instance has the same assumptions as its body.
345 precedence
= Basic
.Lambda_precedence
347 has_derivative
= True
349 def __new__(cls
, expr
, *args
):
350 expr
= Basic
.sympify(expr
)
351 args
= tuple(map(Basic
.sympify
, args
))
352 #if isinstance(expr, Apply):
357 if not isinstance(a
, Basic
.Symbol
):
358 raise TypeError("%s %s-th argument must be Symbol instance (got %r)" \
359 % (cls
.__name
__, len(dummy_args
)+1,a
))
361 expr
= expr
.subs(a
, d
)
363 obj
= Basic
.__new
__(cls
, expr
, *dummy_args
, **expr
._assumptions
)
366 def _hashable_content(self
):
371 return len(self
._args
)-1
373 def __getitem__(self
, iter):
374 return self
._args
[1:][iter]
383 def tostr(self
, level
=0):
384 precedence
= self
.precedence
385 r
= 'lambda %s: %s' % (', '.join([a
.tostr() for a
in self
]),
386 self
.body
.tostr(precedence
))
387 if precedence
<= level
:
392 return '%s(%s)' % (self
.__class
__.__name
__, ', '.join([a
.torepr() for a
in self
]))
394 def as_coeff_terms(self
, x
=None):
395 c
,t
= self
.body
.as_coeff_terms(x
)
396 return c
, [Lambda(Basic
.Mul(*t
),*self
[:])]
398 def _eval_power(b
, e
):
400 (lambda x:f(x))**e -> (lambda x:f(x)**e)
402 return Lambda(b
.body
**e
, *b
[:])
404 def _eval_fpower(b
, e
):
406 FPow(lambda x:f(x), 2) -> lambda x:f(f(x)))
408 if isinstance(e
, Basic
.Integer
) and e
.is_positive
and e
.p
< 10 and len(b
)==1:
410 for i
in xrange(e
.p
-1):
412 return Lambda(r
, *b
[:])
414 def with_dummy_arguments(self
, args
= None):
416 args
= tuple([a
.as_dummy() for a
in self
])
417 if len(args
) != len(self
):
418 raise TypeError("different number of arguments in Lambda functions: %s, %s" % (len(args
), len(self
)))
420 for a
,na
in zip(self
, args
):
421 expr
= expr
.subs(a
, na
)
424 def _eval_expand_basic(self
, *args
):
425 return Lambda(self
.body
._eval
_expand
_basic
(*args
), *self
[:])
427 def diff(self
, *symbols
):
428 return Lambda(self
.body
.diff(*symbols
), *self
[:])
430 def fdiff(self
, argindex
=1):
431 if not (1<=argindex
<=len(self
)):
432 raise TypeError("%s.fderivative() argindex %r not in the range [1,%s]"\
433 % (self
.__class
__, argindex
, len(self
)))
435 expr
= self
.body
.diff(s
)
436 return Lambda(expr
, *self
[:])
438 _eval_subs
= Basic
._seq
_subs
440 def canonize(cls
, *args
):
443 raise TypeError('%s takes exactly %s arguments (got %s)'\
444 % (cls
, n
, len(args
)))
446 for da
,a
in zip(cls
, args
):
447 expr
= expr
.subs(da
,a
)
450 class Derivative(Basic
, ArithMeths
, RelMeths
):
452 Carries out differentation of the given expression with respect to symbols.
454 expr must define ._eval_derivative(symbol) method that returns differentation result or None.
456 Derivative(Derivative(expr, x), y) -> Derivative(expr, x, y)
459 precedence
= Basic
.Apply_precedence
461 def __new__(cls
, expr
, *symbols
, **assumptions
):
462 expr
= Basic
.sympify(expr
)
463 if not symbols
: return expr
464 symbols
= map(Basic
.sympify
, symbols
)
466 if not assumptions
.get("evaluate", True):
467 obj
= Basic
.__new
__(cls
, expr
, *symbols
)
471 assert isinstance(s
, Basic
.Symbol
),`s`
475 unevaluated_symbols
= []
477 obj
= expr
._eval
_derivative
(s
)
479 unevaluated_symbols
.append(s
)
483 if not unevaluated_symbols
:
485 return Basic
.__new
__(cls
, expr
, *unevaluated_symbols
)
488 # Derivative(f(x),x) -> Apply(Lambda(f(_x),_x), x)
491 for s
in self
.symbols
:
494 indices
.append(len(symbols
))
496 indices
.append(symbols
.index(s
)+1)
498 return Apply(FApply(FDerivative(*indices
), Lambda(self
.expr
, *symbols
)), *symbols
)
500 def _eval_derivative(self
, s
):
505 if s
not in self
.symbols
:
506 obj
= self
.expr
.diff(s
)
507 if isinstance(obj
, Derivative
):
508 return Derivative(obj
.expr
, *(obj
.symbols
+self
.symbols
))
509 return Derivative(obj
, *self
.symbols
)
510 return Derivative(self
.expr
, *((s
,)+self
.symbols
), **{'evaluate': False})
513 return Derivative(self
.expr
, *self
.symbols
)
521 return self
._args
[1:]
523 def tostr(self
, level
=0):
524 r
= 'D' + `
tuple(self
)`
525 if self
.precedence
<= level
:
529 def _eval_subs(self
, old
, new
):
530 return Derivative(self
[0].subs(old
, new
), *self
[1:])
532 def matches(pattern
, expr
, repl_dict
={}, evaluate
=False):
533 # this method needs a cleanup.
535 #print "? :",pattern, expr, repl_dict, evaluate
538 for p
,v
in repl_dict
.items():
540 if v
==expr
: return repl_dict
542 assert isinstance(pattern
, Derivative
)
543 if isinstance(expr
, Derivative
):
544 if len(expr
.symbols
) == len(pattern
.symbols
):
545 #print "MAYBE:",pattern, expr, repl_dict, evaluate
546 return Basic
.matches(pattern
, expr
, repl_dict
, evaluate
)
547 #print "NONE:",pattern, expr, repl_dict, evaluate
549 #print pattern, expr, repl_dict, evaluate
551 if pattern
.nofargs
is not None:
552 if pattern
.nofargs
!= expr
.nofargs
:
554 repl_dict
= repl_dict
.copy()
555 repl_dict
[pattern
] = expr
559 def diff(f
, x
, times
= 1, evaluate
=True):
560 """Differentiate f with respect to x
562 It's just a wrapper to unify .diff() and the Derivative class,
563 it's interface is similar to that of integrate()
565 see http://documents.wolfram.com/v5/Built-inFunctions/AlgebraicComputation/Calculus/D.html
569 for i
in range(0,times
):
573 return Derivative(f
, x
, evaluate
=evaluate
)
577 # TODO rename me to something more appropriate? e.g. ArithFunction (or just
579 class SingleValuedFunction(ArithMeths
, Function
):
581 Single-valued functions.
585 def _eval_apply_evalf(cls
, arg
):
588 #if cls.nofargs == 1:
589 # common case for functions with 1 argument
590 #if isinstance(arg, Basic.Number):
592 func_evalf
= getattr(arg
, cls
.__name
__)