# old wiki Sympy Research

Alexey U. Gudchenko edited this page Dec 2, 2011 · 4 revisions

old wiki

# Introduction

Usage:

simply checkout the trunk, most things should be working the same way as before.

# Mathematical constants

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()`.

## Examples

``````>>> 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)
``````

# Assigning properties to symbols and expressions

## Construction call

(, property1=value1, property2, ..)

## Method call

`<expr>.assume(property1=value1, property2=value2, ..)`

## Parameters

• `property1, ...` --- names of mathematical properties
• `value1, ...` --- `True` or `False` or `None`

## Details

• The following predefined mathematical propeties are supported (opposite properties are given in parenthesis):

1. `positive` (`nonpositive`), `negative` (`nonnegative`) --- `expr` can have only positive or negative real values, respectively
2. `complex` (`noncomplex`) --- `expr` has values from the set of complex numbers
3. `real` (`nonreal`) --- `expr` has values from the set of real numbers
4. `rational` (`irrational`) --- `expr` has values from the set of rational numbers
5. `integer` (`noninteger`) --- `expr` has values from the set of integers
6. `odd` (`even`) --- `expr` value is an odd integer
7. `prime` (`composite`) --- `expr` value is a prime
8. `zero` (`nonzero`) --- `expr` value is zero
9. `bounded` (`unbounded`) --- `expr` absolute value is bounded
10. `finite` (`infinitesimal`) --- `expr` value is nonzero and finite
11. `commutative` (`noncimmutative`) --- `expr` value commutes with other values with respect to multiplication
12. `homogeneous` (`inhomogeneous`) --- `expr` value is a homogeneous function with respect to its arguments
13. `comparable` --- `expr` value can be compared with numbers (`expr.evalf()` returns `Number` object).
14. `pi` is alias to `integer`, `positive` properties
15. `nni` is alias to `integer`, 'nonnegative' properties
• If property value is `False`, the opposite property is set. For example, setting `positive=False` is equivalent to `nonpositive=True`.

• Assigned properties can be unset using property value `None`.

• Unsetting a property will unset related properties. For example, unsetting `real` will also unset `positive` 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 is `None`.

• `expr` properties can be set using `property=value` keyword arguments in the corresponding class constructor.

## Rules

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^ to `True` or `False`, then P is `True`
• if `x` sets P^i^ to `None`, then P keeps its value.
• if `x` sets P to `True`, P^i^ is not modified (it's probably `None`)
• if `x` sets P to `False` or `None`, then P^i^ is set to `None`.
• if `x` sets P^1^ to `False` or `True` or `None`, then its opposite property `_P_^2^` is set to `True` or `False` or `None`, respectively.

## Examples

``````>>> 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 – the domain of order terms (Landau symbols)

`O(f, x)` represents the Landau symbol O(f, x -> 0).

## Call

`O(f, x, y, ..)`

## Parameters

• `f` --- an arithmetical expression representing a function in x, y, etc.
• `x, y` --- variables, assumed to be infinitesimal and positive symbols

## Return value

an `O` or `Zero` instance.

## Details

• 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 as `O(f(x,y)+g(x,y), x,y)`.

• If variables `x, y, ..` are not specified then all symbols in `f` are considered as order variables. Otherwise all other symbols in `f` except `x, 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) and `O(f,x)+O(g,x)` results `O(f,x)`.

• Automatic simplification is applied to `O` expression. See examples.

• Assumptions `infinitesimal=True, positive=True` are set to `O` 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.

## Examples

``````>>> 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)
``````

# Functions

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.

# Developers notes

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` and `Mul` 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 in `Mul(2,x)`. In this situation, `sympy.core` will never create an intermediate object representing `x+x` but carries out evaluation and then creates a final object representing `2*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 with `obj is S.One` or `isinstance(obj, Basic.One)` or `obj.is_one`, that involve only single operations, for efficency. Note that executing `obj==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 as `Add` and `Mul`. The order of symbolic objects is first defined by the class name in the `ordering_of_classes` list in `basic_methods.py` file, and then by the order of tuple representation of symbolic objects returned by the `._hashable_content()` methods. See the implementation of `Basic.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 all `Basic` subclasses to be attributes of the `Basic` class at the moment of defining the subclasses. As a result, one only needs to import `Basic` from `sympy.core.basic` module to get access to all classes that are derived from the `Basic` class.