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
):
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 ---------------------------------------------
162 def canonize(cls, arg):
163 if isinstance(arg, Basic.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))
178 return self
.__class
__
180 def _eval_subs(self
, old
, new
):
183 elif isinstance(old
, FunctionClass
) and isinstance(new
, FunctionClass
):
184 if old
== self
.func
and old
.nofargs
== new
.nofargs
:
186 obj
= self
.func
._eval
_apply
_subs
(*(self
[:] + (old
,) + (new
,)))
189 return Basic
._seq
_subs
(self
, old
, new
)
191 def _eval_expand_basic(self
, *args
):
194 def _eval_evalf(self
):
195 obj
= self
.func
._eval
_apply
_evalf
(*self
[:])
200 def _eval_is_comparable(self
):
201 if isinstance(self
.func
, DefinedFunction
):
210 def _eval_derivative(self
, s
):
211 # Apply(f(x), x).diff(s) -> x.diff(s) * f.fdiff(1)(s)
218 if isinstance(da
, Basic
.Zero
):
220 if isinstance(self
.func
, FunctionClass
):
224 # df = self.func.fdiff(i)
225 # l.append(Apply(df,*self[:]) * da)
228 def _eval_power(b
, e
):
230 return b
.func
._eval
_apply
_power
(b
[0], e
)
233 def _eval_is_commutative(self
):
237 if c
is None: return None
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
):
257 def as_base_exp(self
):
258 return self
, Basic
.One()
260 def count_ops(self
, symbolic
=True):
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`
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
):
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)
281 while not order
.contains(term
):
284 fact
*= Basic
.Rational(i
)
286 term
= e
.subs(x
, Basic
.Rational(0))*(x
**i
)/fact
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
):
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
]
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:
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]
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):
330 r
= '%s(%s)' % (self
.func
.__name
__, ', '.join([a
.tostr() for a
in self
]))
336 def _eval_apply_evalf(cls
, arg
):
339 class WildFunction(Function
, Atom
):
343 def matches(pattern
, expr
, repl_dict
={}, evaluate
=False):
344 for p
,v
in repl_dict
.items():
346 if v
==expr
: return repl_dict
348 if pattern
.nofargs
is not None:
349 if pattern
.nofargs
!= expr
.nofargs
:
351 repl_dict
= repl_dict
.copy()
352 repl_dict
[pattern
] = expr
355 def tostr(self
, level
=0):
356 return self
.name
+ '_'
359 def _eval_apply_evalf(cls
, arg
):
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
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):
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
))
385 expr
= expr
.subs(a
, d
)
387 obj
= Basic
.__new
__(cls
, expr
, *dummy_args
, **expr
._assumptions
)
390 def _hashable_content(self
):
395 return len(self
._args
)-1
397 def __getitem__(self
, iter):
398 return self
._args
[1:][iter]
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
:
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:
434 for i
in xrange(e
.p
-1):
436 return Lambda(r
, *b
[:])
438 def with_dummy_arguments(self
, args
= 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
)))
444 for a
,na
in zip(self
, args
):
445 expr
= expr
.subs(a
, na
)
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
)))
459 expr
= self
.body
.diff(s
)
460 return Lambda(expr
, *self
[:])
462 _eval_subs
= Basic
._seq
_subs
464 def canonize(cls
, *args
):
467 raise TypeError('%s takes exactly %s arguments (got %s)'\
468 % (cls
, n
, len(args
)))
470 for da
,a
in zip(cls
, args
):
471 expr
= expr
.subs(da
,a
)
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
)
495 assert isinstance(s
, Basic
.Symbol
),`s`
499 unevaluated_symbols
= []
501 obj
= expr
._eval
_derivative
(s
)
503 unevaluated_symbols
.append(s
)
507 if not unevaluated_symbols
:
509 return Basic
.__new
__(cls
, expr
, *unevaluated_symbols
)
512 # Derivative(f(x),x) -> Apply(Lambda(f(_x),_x), x)
515 for s
in self
.symbols
:
518 indices
.append(len(symbols
))
520 indices
.append(symbols
.index(s
)+1)
522 return Apply(FApply(FDerivative(*indices
), Lambda(self
.expr
, *symbols
)), *symbols
)
524 def _eval_derivative(self
, s
):
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})
537 return Derivative(self
.expr
, *self
.symbols
)
545 return self
._args
[1:]
547 def tostr(self
, level
=0):
548 r
= 'D' + `
tuple(self
)`
549 if self
.precedence
<= level
:
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
562 for p
,v
in repl_dict
.items():
564 if v
==expr
: return repl_dict
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
573 #print pattern, expr, repl_dict, evaluate
575 if pattern
.nofargs
is not None:
576 if pattern
.nofargs
!= expr
.nofargs
:
578 repl_dict
= repl_dict
.copy()
579 repl_dict
[pattern
] = expr
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
593 for i
in range(0,times
):
597 return Derivative(f
, x
, evaluate
=evaluate
)
601 # TODO rename me to something more appropriate? e.g. ArithFunction (or just
603 class SingleValuedFunction(ArithMeths
, Function
):
605 Single-valued functions.
609 def _eval_apply_evalf(cls
, arg
):
612 #if cls.nofargs == 1:
613 # common case for functions with 1 argument
614 #if isinstance(arg, Basic.Number):
616 func_evalf
= getattr(arg
, cls
.__name
__)