This introduction to SymPy was written using the following sources:
Another important source of documentation about SymPy is the official documentation.
Instructions: Create a new directory called SymPy
with a notebook called SymPyTour
. Give it a heading 1 cell title Symbolic Analysis with SymPy. Read this page, typing in the code in the code cells and executing them as you go.
Do not copy/paste.
Type the commands yourself to get the practice doing it. This will also slow you down so you can think about the commands and what they are doing as you type them.</font>
Save your notebook when you are done, then try the accompanying exercises.
SymPy is a symbolic mathematics package for Python. In symbolic mathematics, the mathematical entities we want to manipulate are not necessarily concrete, specific numbers such as $2.0$. Instead, they can be symbols, such as $x$ or $y$ that only sometimes take on concrete values.
Let's start out by importing necessary packages from IPython:
In [ ]:
from IPython.html.widgets import interact
from IPython.display import display
It is important to not use %pylab inline
when using SymPy. The reason for this is that %pylab
imports names that conflict with those in SymPy. Instead, use %matplotlib inline
and manually import numpy
and matplotlib
.
In [ ]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
The sympy
namespace imports many things when imported using Python's *
notation. You should only do this when you are confident the names imported by sympy
will not conflict with other names in your code.
In [ ]:
from sympy import *
When using SymPy in the IPython Notebook, it is recommended to initialize $\LaTeX$ printing of expressions:
In [ ]:
init_printing(use_latex='mathjax')
In SymPy you need to create symbols for the symbolic variables you want to work with. You can create a new symbol using the Symbol
class:
In [ ]:
x = Symbol('x')
Once you have created symbols, you can use them in mathematical expressions:
In [ ]:
(pi + x)**2
The function IPython.display.display
works just like print, but also handles rich representations such as $\LaTeX$. This is convenient as you can use display
inside loops and other structures.
In [ ]:
display((pi + x)**2)
You can create a set of symbolic using the symbols
function:
In [ ]:
a, b, c = symbols("a, b, c")
You can add mathematical assumptions to symbols when you create them by passing in keyword arguments. This creates the symbol x
with the assumption that it is a real number:
In [ ]:
x = Symbol('x', real=True)
The set of all assumptions about a symbol can be probed using a set of attributes with the naming convention is_[assumption]
:
In [ ]:
x.is_imaginary
When an assumption not determined, it will return None
:
In [ ]:
x.is_positive
In [ ]:
x = Symbol('x', positive=True)
In [ ]:
x > 0
Assumptions propagate through mathematical expressions and are computed dynamically. Negating a positive number gives a negative one:
In [ ]:
(-x).is_positive, (-x).is_negative
Here is a list of all assumptions that symbols know about. What happens if you remove the k == k.lower()
test from this code? What do you think those additional is_*
attributes correspond to?
In [ ]:
for k in dir(x):
if k.startswith('is_') and k == k.lower():
print k
The imaginary unit is denoted I
in SymPy:
In [ ]:
1+1*I
In [ ]:
I**2
In [ ]:
I.is_imaginary
In [ ]:
(x*I + 1)**2
The abs
function will take the complex absolute value:
In [ ]:
abs(x*I + 1)
There are three basic numerical types in SymPy:
Real
Rational
Integer
The Real
and Integer
classes for SymPy are different from Python's builtin float
and int
types. However, you will almost never need to manually create Real
and Integer
instances. Any time a Python float
or int
appears in a symbolic expression, they are converted to a SymPy Real
or Integer
instance:
In [ ]:
srepr(2.0*x)
When working with numerical types in SymPy it is important to carefully distinguish SymPy types from the native Python numerical types (float
and int
). Python's builtin type
function can be used to show the type of an object. You can use sympify
to create a SymPy type from a Python type. To go in the other direction, using Python's builtin float
or int
:
In [ ]:
print type(1.0)
print type(sympify(1.0))
print type(float(sympify(1.0)))
Rational numbers must be created manually by passing the numerator and denominator to the Rational
class:
In [ ]:
r1 = Rational(4,5)
r2 = Rational(5,4)
Basic arithmetic operations work with rational numbers:
In [ ]:
r1
In [ ]:
r1+r2
In [ ]:
sqrt(r1/r2)
In [ ]:
r1.is_rational, r1.is_Rational
SymPy integers and real numbers support arbitrary precision. To evaluate a symbolic expression numerically to a given precision use the evalf
method:
In [ ]:
pi.evalf(n=50)
You can also use the N
function to accomplish the same thing:
In [ ]:
y = (x + pi)**2
In [ ]:
N(y, 5)
When you numerically evaluate algebraic expressions you often want to substitute a symbol with a numerical value. You can do that using the subs
function:
In [ ]:
y.subs(x, 1.5)
In [ ]:
N(y.subs(x, 1.5))
The subs
function can also be used to substitute Symbols and other expressions:
In [ ]:
y.subs(x, a+pi)
You can also pass subs
a dict
of keys and values to be substituted:
In [ ]:
e = x*y
In [ ]:
e.subs({x:4, y:pi})
Often times, it is useful to convert from a SymPy expression to a Python function that can be used with NumPy arrays. This can be done using SymPy's lambdify
function in the following manner:
In [ ]:
z = Symbol('z')
f = lambdify([z], cos(pi*z)**2, modules='numpy')
x = np.linspace(0.0, 2*np.pi, 100)
y = f(x)
plt.plot(x, y)
SymPy has a number of powerful functions and methods for performing algebraic manipulations.
In [ ]:
x, y, z = symbols('x, y, z')
The expand
and factor
functions are roughly inverses of each other. The expand
function will expand a product of expressions into a sum of expression:
In [ ]:
e = (x+1)*(x+2)*(x+3)
e
In [ ]:
expand(e)
factor
does the inverse operation:
In [ ]:
factor(_)
expand
also has a number of keyword arguments that enable it to expand other types of mathematical expressions, such as trigonometric function or complex numbers:
In [ ]:
e = sin(a+b)
In [ ]:
expand(e)
In [ ]:
expand(e, trig=True)
In [ ]:
expand((x+y)**3)
In [ ]:
expand(x+y, complex=True)
A common task in symbolic mathematics is to "simplify" an expression. SymPy has a simplify
function that applies a set of transformations using heuristics in an attempt to produce the most simplified version of an expression:
In [ ]:
simplify((x+x*y)/x)
In [ ]:
simplify(sin(a)**2 + cos(a)**2)
In [ ]:
simplify(cos(x)/sin(x))
When working with polynomials, it is often useful to pick out terms that are powers of a given expression. This can be done with collect
:
In [ ]:
e = x*y + x - 3 + 2*x**2 - z*x**2 + x**3
In [ ]:
collect(e, x)
The coeff
method will return the coefficient of a given term:
In [ ]:
collect(e, x).coeff(x, 2)
The cancel
function will try to cancel common factors in fractional expressions:
In [ ]:
cancel((x**2 + 2*x + 1)/(x**2 + x))
In [ ]:
cancel((exp(x)+2.0*exp(x))/(exp(x)))
SymPy has a number of other functions for performing algebraic manipulations:
trigsimp
expand_trig
powsimp
expand_power_exp
expand_power_base
powdenest
expand_log
logcombine
rewrite
expand_func
hyperexpand
combsimp
SymPy is able to perform many operations from calculus using symbolic expressions. This includes differentiation, integration, Taylor series, limits, sums and products.
In [ ]:
x, y, z = symbols('x, y, z')
a, b, c = symbols('a, b, c')
You can take a deriviate using the diff
function:
In [ ]:
diff(a*x**2, x)
In [ ]:
diff(cos(a*x+b), x)
Derivatives of higher orders are specified by a third argument to diff
. Here is a second derivative:
In [ ]:
diff(cos(a*x+b), x, 2)
diff
can also be used to take partial derivatives:
In [ ]:
f = sin(x*y) + cos(y*z)
In [ ]:
diff(f, x)
In [ ]:
diff(f, y)
In [ ]:
diff(f, z)
Note that diff
takes a derivative immediately. To construct an unevaluated derivative, use the Derivative
objects, which accepts the same arguments as diff
. Here is a mixed second partial derivative:
In [ ]:
d = Derivative(f, x, 1, y, 1)
An unevaluated derivative can be evaluated using the doit()
method:
In [ ]:
d.doit()
Symbolic integrals can be done using the integrate
function:
In [ ]:
integrate(6*x**5, x)
In [ ]:
integrate(sin(x), x)
In [ ]:
integrate(log(x), x)
In [ ]:
integrate(2*x + sinh(x), x)
SymPy also knows many integrals involving special functions:
In [ ]:
integrate(exp(-x**2)*erf(x), x)
Definite integrals can be done by specifying limits in the form (variable, min, max
):
In [ ]:
integrate(x**3, (x, -1, 1))
In [ ]:
integrate(sin(x), (x, 0, pi/2))
In [ ]:
integrate(cos(x), (x, -pi/2, pi/2))
In SymPy the symbol for $\infty$ is oo
(two lowercase "o" characters). This allows integrals over infinite ranges:
In [ ]:
integrate(exp(-x), (x, 0, oo))
In [ ]:
integrate(exp(-x**2), (x, -oo, oo))
Double integrals can be performed by giving a sequence of variables and/or ranges to integrate over. In this case, the Integral
class is used to construct an unevaluated integral:
In [ ]:
i = Integral(exp(-x**2 - y**2), (x,-oo,oo), (y,-oo,oo))
i
Again, the doit()
method performs the integrals:
In [ ]:
i.doit()
Finite and infinite sums and products can be construted, in both evaluated and unevaluated forms.
In [ ]:
n = Symbol('n')
Use the summation
function to directly perform a sum:
In [ ]:
s = summation(1/n**2, (n, 1, 10))
s
Here is an unevaluated infinite sum:
In [ ]:
s = Sum(1/n**2, (n, 1, oo))
The doit()
method will attempt to produce an exact, symbolic answer:
In [ ]:
s.doit()
If that is not possible, you can always use the evalf
method to obtain a numerical answer to a given precision:
In [ ]:
s.evalf()
Here is an unevaluated product:
In [ ]:
p = Product(n, (n, 1, 10))
p
In [ ]:
p.doit()
Right now evalf
doesn't work with Product
, which looks like a bug:
In [ ]:
p.evalf()
SymPy is capable of taking limits symbolically using the limit
function:
In [ ]:
limit(sin(x)/x, x, 0)
Let's use the definition of the derivative as a limit to check the derivative of this function:
In [ ]:
f
In [ ]:
diff(f, x)
In [ ]:
h = Symbol('h')
In [ ]:
df_dx = (f.subs(x, x+h) - f)/h
df_dx
Let's construct an unevaluated limit using the Limit
class and then take the limit $h\rightarrow0$ to recover the exact answer above:
In [ ]:
l = Limit(df_dx, h, 0)
l
In [ ]:
l.doit()
Some other examples of limits:
In [ ]:
limit(x, x, oo)
In [ ]:
limit(1/x, x, oo)
limit
and Limit
also accept a dir
keyword argument that can be used to specify the direction of the limit:
In [ ]:
limit(1/x, x, 0, dir='+')
In [ ]:
limit(1/x, x, 0, dir='-')
SymPy can perform Taylor series about arbitrary points using the series
function.
Expand the function $e^x$ about the default point ($x=0$) to the default order (5):
In [ ]:
series(exp(x), x)
Here is the Taylor series about zero, up to $7th$ order:
In [ ]:
series(exp(x), x, 0, 7)
Let's plot the series approximation alongside the exact function to get a sense of how well the approximate works. Note how we use the removeO
method to remove the $\mathcal{O}(x^7)$ term:
In [ ]:
plot(exp(x), _.removeO(), (x,0-5,0+5))
SymPy can solve algebraic equations symbolically using the solve
function.
In [ ]:
x, y, a, b = symbols('x, y, a, b')
If solve is passed an expression, it is assumed that expression is equal to $0$ when converting it to an equation.
In [ ]:
solve(x**4 - 1, x)
Note how in this form, solve
returns a list of solutions. In general, the number of solutions will correspond to the order of the equation.
If solve
is passed dict=True
, it will return a dictionary of solutions. This can be useful if you want to substitute the solutions later using subs
.
In [ ]:
solve(x**4 - 1, x, dict=True)
You can also pass solve
an equation object constructed using the Eq
class. This let's you specify non-zero right and left hand sides of the equation:
In [ ]:
eq = Eq(x**4, 1)
eq
In [ ]:
soln = solve(eq, dict=True)
soln
We can then substitute the solutions to verify that they solve the orginal equation:
In [ ]:
[eq.subs(s) for s in soln]
You can also pass a list of equations and variables to solve for:
In [ ]:
solve([x + 5*y - 2, -3*x + 6*y - 15], [x, y])
In most cases, you will use NumPy for linear algebra. This works well if each element of your arrays is a concrete number. However, in some cases you will want to work with matrices of symbolic expressions. That can be done using the SymPy Matrix
class. The API presented by the Matrix
class is very similar to that of NumPy
.
In [ ]:
x = Symbol('x')
y = Symbol('y')
In [ ]:
A = Matrix([[1,x], [y,1]])
A
In [ ]:
A[0,0]
In [ ]:
A[:,1]
You can create symbolic expressions using these matrices:
In [ ]:
A**2
Here is a symbolic matrix inverse:
In [ ]:
A.inv()
For small matrices, you can even find eigenvalues symbolically:
In [ ]:
A.eigenvals()
The subs
method works as expected:
In [ ]:
A.subs({x:0,y:I})