-
Notifications
You must be signed in to change notification settings - Fork 0
old wiki Sympy Research
old wiki
This page collects documentation notes for the new trunk, which merged new core developed by Pearu with !SymPy. More information can be found in [http://code.google.com/p/sympy/issues/detail?id=144 Issue 144]
Usage:
simply checkout the trunk, most things should be working the same way as before.
The following mathematical constants are defined in sympy
:
- I --- imaginary unit
sqrt(-1)
- oo --- positive infinity
- nan --- undefined value
- pi --- pi = 3.14..
- E, exp(1) --- Euler number E = 2.718.., exp(1)
Use .evalf()
method to get floating point approximation with the current precision Basic.set_precision()
.
>>> Basic.set_precision()
28
>>> pi.evalf(), E.evalf()
(3.141592653589793238462643383, 2.718281828459045235360287471)
>>> Basic.set_precision(40)
28
>>> pi.evalf(), E.evalf()
(3.141592653589793238462643383279502884197,
2.718281828459045235360287471352662497757)
>>> I,I**2,oo,nan
(I, -1, oo, nan)
(, property1=value1, property2, ..)
<expr>.assume(property1=value1, property2=value2, ..)
-
property1, ...
--- names of mathematical properties -
value1, ...
---True
orFalse
orNone
-
The following predefined mathematical propeties are supported (opposite properties are given in parenthesis):
-
positive
(nonpositive
),negative
(nonnegative
) ---expr
can have only positive or negative real values, respectively -
complex
(noncomplex
) ---expr
has values from the set of complex numbers -
real
(nonreal
) ---expr
has values from the set of real numbers -
rational
(irrational
) ---expr
has values from the set of rational numbers -
integer
(noninteger
) ---expr
has values from the set of integers -
odd
(even
) ---expr
value is an odd integer -
prime
(composite
) ---expr
value is a prime -
zero
(nonzero
) ---expr
value is zero -
bounded
(unbounded
) ---expr
absolute value is bounded -
finite
(infinitesimal
) ---expr
value is nonzero and finite -
commutative
(noncimmutative
) ---expr
value commutes with other values with respect to multiplication -
homogeneous
(inhomogeneous
) ---expr
value is a homogeneous function with respect to its arguments -
comparable
---expr
value can be compared with numbers (expr.evalf()
returnsNumber
object). -
pi
is alias tointeger
,positive
properties -
nni
is alias tointeger
, 'nonnegative' properties
-
-
If property value is
False
, the opposite property is set. For example, settingpositive=False
is equivalent tononpositive=True
. -
Assigned properties can be unset using property value
None
. -
Unsetting a property will unset related properties. For example, unsetting
real
will also unsetpositive
property, for instance. -
expr
properties can be requested via.is_property
attributes. If no information is known for a given property then.is_property
attribute isNone
. -
expr
properties can be set usingproperty=value
keyword arguments in the corresponding class constructor.
From a set of properties on can derive more properties. For example, if
an expression has property positive
then it follows that the expression
has also a property being real
(as the set of positive numbers is a subset of
real numbers) as well as being complex
(as the set of real numbers
is a subset of complex numbers), etc. In general, a property, say P,
may be a union of subproperties, say P^1^, P^2^, etc. For example, if
P is real
then P^1^ and P^2^ can be positive
and nonpositive
, respectively.
Each property has opposite property, e.g. the opposite property of odd
is even
.
The following rules apply for derived properties:
- if
x
sets P^i^ toTrue
orFalse
, then P isTrue
- if
x
sets P^i^ toNone
, then P keeps its value. - if
x
sets P toTrue
, P^i^ is not modified (it's probablyNone
) - if
x
sets P toFalse
orNone
, then P^i^ is set toNone
. - if
x
sets P^1^ toFalse
orTrue
orNone
, then its opposite property_P_^2^
is set toTrue
orFalse
orNone
, respectively.
>>> x = Symbol('x')
>>> x.is_real
>>> x.assume(positive=True)
>>> x.is_real
True
>>> x.is_negative
False
>>> exp(x).is_positive
True
>>> log(x).is_negative
>>> x.assume(infinitesimal=True)
>>> log(x).is_negative
True
>>> y = Symbol('y', even=True)
>>> y.is_integer
True
>>> y.is_odd
False
O(f, x)
represents the Landau symbol O(f, x -> 0).
O(f, x, y, ..)
-
f
--- an arithmetical expression representing a function in x, y, etc. -
x, y
--- variables, assumed to be infinitesimal and positive symbols
an O
or Zero
instance.
-
According to the mathematical definition, for a function f with variables x,y, .., the Landau symbol g = O(f, x, y, ..) is a representative of a class of functions having the following property: there exists a constant M and a neighborhood of the limit point (0, 0, ..) such that |g| <= M|f| for all values (x,y,..) in that neighborhood. Note that in general
O(f(x,y),x,y) + O(g(x,y),x,y)
is not the same asO(f(x,y)+g(x,y), x,y)
. -
If variables
x, y, ..
are not specified then all symbols inf
are considered as order variables. Otherwise all other symbols inf
exceptx, y, ..
are considered as parameters. -
The symbols in
O
instance can be accessed via.symbols
attribute. -
The expression in
O
instance can be accessed via.expr
attribute. -
The inclusion relation between O(f, x) and O(g, x) is determined by the result of
limit(f/g, x, 0)
. If the result is unbounded (e.g.oo
) then O(g, x) is a subset of O(f, x) andO(f,x)+O(g,x)
resultsO(f,x)
. -
Automatic simplification is applied to
O
expression. See examples. -
Assumptions
infinitesimal=True, positive=True
are set toO
symbols. -
Using
O
in the argument expression of a function leads to either Add or Order instance after applying the rule: f(g(x) + O(h(x),x)) -> f(g(x)) + O(h(x) f'(g(x)), x) provided that f'(g(x)) is not zero.
>>> from sympy.core import *
>>> x,y=Symbol('x'), Symbol('y')
>>> O(x,x),O(3*x,x),O(x+x**2),O(x+1/x),O(x+1/x+exp(2/x))
(O(x, x), O(x, x), O(x, x), O(1 / x, x), O(exp(2 / x), x))
>>> O(x+3*y), O(x+x*y), O(3*x*y**2-x**2*y+x**2*y**2), O(y**2*x**2+x*y**3,y)
(O(x + y, x, y), O(x, x), O(x * y ** 2 + y * x ** 2, x, y), O(y ** 2, y))
>>> O(x*y).expr, O(x*y).symbols
(x * y, (x, y))
>>> O(0,x), O(2,x)
(0, O(1))
>>> O(x) + O(y), O(x+y)
(O(x, x) + O(y, y), O(x + y, x, y))
>>> exp(1+x+O(x**3))
exp(1 + x) + O(x ** 3, x)
>>> cos(O(x))
1 + O(x ** 2, x)
To define a new function in !SymPy, a three step process is needed. First of all we need to create the function with its name, the number of arguments, default properties, series handling and, especially, evaluation procedure. As an example consider the following code:
class Im(DefinedFunction):
nofargs = 1
is_real = True
def _eval_apply(self, arg):
arg = Basic.sympify(arg)
if isinstance(arg, Basic.NaN):
return S.NaN
elif arg.is_real:
return S.Zero
This way new function called Im has been defined (which stands here for the simplified version of an imaginary part function, which is implemented in sympy/functions/elementary/complexes.py
). Later this function will be identified via this name in lower case form.
The number of obligatory arguments is specified by setting nofargs
class member. Additional arguments do not account here, but they will be visible in _eval_apply
method. For example logarithm has fnoargs=1
for primary expression but you can also pass its base as an additional parameter. However logarithm function will rewrite itself to form with exactly one parameter (using the base change formula).
If function has properties which are valid for all arguments, you can set these using assumptions mechanism. In case of imaginary part, I've set is_real=True
as this is what always holds.
Now the most important part of our function is evaluation procedure, _eval_apply
, which will have exactly nofargs
arguments without default value, plus additional with default value set to None
. It's recommended, as the first step in this procedure, to check if all arguments are !SymPy compatible using Basic.sympify()
. This step is a cheap if those arguments are already derived from the Basic class.
Next step is to analyze the arguments. First we needed to get rid of the optional ones. We must check for them and then rewrite the function so that only obligatory arguments will be present. If this is done, the next step is to put function into a normal form. What this term means depends on a concrete function and its implementation. However, the general rule is to perform non-expensive rewriting to function's arguments, which means you must avoid using complex procedures like polynomial algorithms, expand()
, combine()
functions and any general simplification procedures like simplify()
. What you should use are all representation functions (those which begin with as_
, also functions like has()
, atoms()
and eventually match()
.
If any reasonable rewrite is possible, then the function should return new value if function invocation is no more needed or self with new arguments otherwise. If no rewrite is possible for some classes of arguments then _eval_apply
should return nothing ie. no return at all, or stand alone return statement or return None. In all three cases the super class, which is DefinedFunction
, will fall back to the original function with arguments left untouched, for efficiency reasons.
If you follow the above steps you will get a !SymPy compatible function. However its capabilities are quite limited. What you can improve is to define its derivative and inverse, add floating point evaluation, power series expansion, non-default handling of value substitution etc.
It is important to notice that thte function defined is just an ordinary Python object so it is created with following syntax: function_name()
with no arguments eg. Im()
. One must not mix this with application of arguments to a function, which is done with calling an instance of defined function class ie. Im()(x)
where x
is a symbol. This way instance of Apply
class is created which will handle 'runtime' properties of this function. Try in isympy shell the following:
>>> type(Basic.Log())
<class 'sympy.core.defined_function.Log'>
>>> type(Basic.Log()(x))
<class 'sympy.core.defined_function.ApplyLog'>
For usage simplicity Basic
class provides singleton
attribute, which is a mapping of simple names to instances of defined functions. For example you can write just log
for the logarithm function rather than Log()
. However notice that this is available only on run time and in library code you are obliged to use the full form.
When you issue log(x) in isympy shell or Log()(x)
in library code, new instance of Apply class is created with first argument being the logarithm function and obligatory and also additional parameters.
Having only an instance of defined function class we are able to work with the function itself eg. we can compute function derivative. However the result might be interesting:
>>> log.fdiff()
lambda _x: 1 / _x
or
>>> sin.fdiff()
cos
In the first example we got anonymous function (or just lambda for functional programmers). In the other we got another defined function. In both cases the result is unevaluated function (notice the f
in front of diff
). To compute the well known derivative we need function application rather than definition, eg.
>>> log.fdiff()(x)
1 / x
>>> log(y).diff(y)
1 / y
or
>>> sin(2*x).diff(x)
2*cos(x)
By default instance of Apply
is created. You can change this behavior by overriding this class and implementing new specific one. Going back to the very first example, we can define the following:
class ApplyIm(Apply):
def _eval_is_real(self):
return True
def _eval_complex_expand(self):
return self.func(self.args[0].as_real_imag()[1])
When implementing such class we care about the runtime behavior of our defined function. Previously we stated that is_real=True
. However now we have to restate the same in _eval
form.
Notice also _eval_complex_expand
method. Finally we have access to function's arguments and its name, which will be im
in this case. What can be implemented more here are all functions with names beginning with _eval_*
(with exceptions like _eval_apply
and _eval_apply_evalf
). You can also implement representations functions here (those with begin with as_
) and tostr()
method.
When you are finished with implementation, remember to add both new classes to ordering_of_classes
list in core/basic_methods.py
in appropriate places. This will help !SymPy properly display new functions in combination with all other objects.
The last step is to write tests for all functionalities you have implemented. Especially great care must be taken in testing evaluation procedure. For this there is separate test suit in core/tests/test_eval_apply.py
. One general rule applies, all cases must be covered with at least one test. This way when we modify this function's logic or we change something very different we will be sure that with those modifications functions are evaluated properly. Of course the same applies to other code too.
Currently, there are all elementary functions implemented (exponential, trigonometric, hyperbolic and their inverses), complex components (real and imaginary parts, conjugate, argument and absolute value) and integer functions like floor and ceiling. All of these are located in core/defined_functions.py
. There is also a preliminary support for some special functions ie. complete and incomplete gamma and zeta, and combinatorial functions, in specfun module. In most cases symbolic evaluation is done, however improvements are needed to power series expansions (especially handling of singularities) and assumptions.
Below some design decisions in the sympy.core
are explained.
sympy.core
uses a symbolic model where all symbolic objects are immutable,
they are constructed from classes that are derived from Basic
class.
For example, sympy.core defines the following classes for elementary symbolic manipulations: Symbol
, Number
, Add
, Mul
, Pow
, Function
, Apply
.
There is a number of other classes derived from Basic or classes listed above
for adding more features to the used symbolic model. For example, Symbol
is
a base class to Wild
, Temporary
and Number
is a base class to Real
,
Rational
, and Integer
classes.
The implementation of the symbolic model uses many advanced features from the Python language such as newstyle classes, metaclasses, properties, decorators, etc. The goal is to keep code minimal, readable, and to get maximum performance using pure Python code while keeping in mind that in future some computationally intensive code should be implemented in a C/C++ extension module.
For efficiency, the following is taken into account:
-
In pure Python constructing an object is an expensive operations. This is especially true for symbolic objects that may need to evaluate their arguments to some normal form. For example, constructing a rational number involves finding gcd of its numerator and denominator. In constructing
Add
andMul
instances some elementary simplifications (evaluations) are carried out. Therefore, many frequently used symbolic notions like numbers are created only once (via singleton or caching technique) and computed results that might be needed in further operations are cached. -
Also, the result of an evaluation may be different from the original constructor. For example,
Add(x, x)
will result inMul(2,x)
. In this situation,sympy.core
will never create an intermediate object representingx+x
but carries out evaluation and then creates a final object representing2*x
. -
Comparing symbolic objects for an equality can be very expensive as one may need to walk through large trees of symbolic objects. Therefore, the code must be optimized for elementary comparisons such as if a symbolic object is an instance of some special number, etc. So, a codelet like
obj == 1
should be replaced withobj is S.One
orisinstance(obj, Basic.One)
orobj.is_one
, that involve only single operations, for efficency. Note that executingobj==1
involves a number of operations, it is equivalent to the following code:Equality(obj, Basic.sympify(1)).__nonzero__()
where the__nonzero__
code contains comparison code for symbolic objects. -
In
sympy.core
the comparison of symbolic objects takes advantage of the fact that all symbolic objects are strictly ordered (not always in mathematical sense, of course). This is achived by sorting the content of otherwise commutative operations such asAdd
andMul
. The order of symbolic objects is first defined by the class name in theordering_of_classes
list inbasic_methods.py
file, and then by the order of tuple representation of symbolic objects returned by the._hashable_content()
methods. See the implementation ofBasic.compare
for more details.
For extending the symbolic model, the following is taken into account:
- Different symbolic classes may need to have access to each other features while the classes are implemented in different modules or even in different packages. Therefore one must be careful with importing modules as in the given situation cyclic imports are introduced.
sympy.core
resolves this issue by making allBasic
subclasses to be attributes of theBasic
class at the moment of defining the subclasses. As a result, one only needs to importBasic
fromsympy.core.basic
module to get access to all classes that are derived from theBasic
class.