An Overview of SymPy
from sympy import *
x = Symbol('x')
x
The variable $x$ now represents an abstract mathematical symbol which could, for example, represent a real number, an integer, a complex number, a function, as well as a large number of other possibilities.
If we have a mathematical variable $y$ that is known to be a real number, we can use the real=True
keyword argument when creating the corresponding symbol instance. We can verify that SymPy indeed recognizes that the symbol is real by using
the is_real
attribute of the Symbol class:
y = Symbol('y', real=True)
y.is_real
If, on the other hand we were to use is_real
to query the previously defined symbol $x$, which was not explicitly specified to real, and therefore can represent both real and nonreal variables, we get None as result:
x.is_real is None
is_real
returns True if the symbol is known to be real, False if the symbol is known to be not real, and None if it is not known if the symbol is real or not
Symbol('z', imaginary=True).is_real
Explicitly specifying when creating new symbols as real and positive or anything else as required, can help SymPy to simplify various expressions further than otherwise possible.
x = Symbol('x')
y = Symbol('y', positive=True)
sqrt(x**2)
sqrt(y**2)
When working with mathematical symbols that represent integers, rather than real numbers, it is also useful to explicitly specify this when creating the corresponding SymPy symbols, using, for example, the integer=True, or even=True or odd=True, if applicable. This may also allow SymPy to analytically simplify certain expressions and function evaluations.
n1 = Symbol('n')
n2 = Symbol('n', integer=True)
n3 = Symbol('n', odd=True)
cos(n1*pi)
cos(n2*pi)
cos(n3*pi)
Using Python’s tuple unpacking syntax together with a call to sympy.symbols
is a convenient way to create multiple symbols:
a, b, c = symbols('a, b, c', negative=True)
d, e, f = symbols('d, e, f', positive=True)
Constants and Special Symbols
Selected mathematical constants and special symbols and their corresponding symbols in SymPy:
Mathematical Symbol | SymPy Symbol | Description |
---|---|---|
$\pi$ | pi |
Ratio of the circumference to the diameter of a circle. |
$e$ | E |
The base of the natural logarithm $e = exp (1)$. |
$\gamma$ | EulerGamma |
Euler's constant |
$i$ | I |
The imaginary unit. |
$\infty$ | oo |
Infinity |
x, y, z = symbols('x, y, z')
f = Function('f')
type(f)
f(x)
g = Function('g')(x, y, z)
g
g.free_symbols
type(sin)
sin(1.5*pi)
n = Symbol('n')
sin(n*pi)
n = Symbol('n', integer=True)
sin(n*pi)
f = Lambda(x, x**2)
f
f(1.5)
f(1 + x)
Expressions
In SymPy, mathematical expressions are represented as trees where leafs are symbols, and nodes are class instances that represent mathematical operations.
Examples of these classes are Add
, Mul
, and Pow
for basic arithmetic operators, and Sum
, Product
, Integral
, and Derivative
for analytical mathematical operations.
x = symbols('x')
expr = 1 + x + x**2 + x**3
expr
expr.args
expr.args[0]
expr.args[1]
expr.args[2]
expr.args[3]
expr = 2 * (x**2 - x) - x * (x + 1)
expr
simplify(expr)
expr.simplify()
expr
expr = 2 * cos(x) * sin(x)
expr
expr.simplify()
expr = exp(x)*exp(y)
expr
simplify(expr)
Each specific type of simplification can also be carried out with more specialized functions, such as sympy.trigsimp
and sympy.powsimp
, for trigonometric and power simplifications, respectively.
Summary of selected SymPy functions for simplifying expressions:
Function | Description |
---|---|
sympy.simplify |
Attempt various methods and approaches to obtain a simpler form of a given expression. |
sympy.trigsimp |
Attempt to simplify an expression using trigonometric identities. |
sympy.powsimp |
Attempt to simplify an expression using laws of powers. |
sympy.compsimp |
Simplify combinatorial expressions. |
sympy.ratsimp |
Simplify an expression by writing on a common denominator |
expr = (x + 1) * (x + 2)
expr
expand(expr)
sin(x + y).expand(trig=True)
log(x * y).expand(log=True)
a, b = symbols('a, b', positive=True)
log(a*b).expand(log=True)
expr = exp(a + I*b)
expr
expr.expand(complex=True)
expr = x**2 - 1
factor(expr)
expr = x*cos(y) + x*sin(z)
factor(expr)
expr = log(a) - log(b)
logcombine(expr)
expr = x + y + x*z + x*y
expr.collect(x)
expr.collect(y)
apart(1/(x**2 + 3*x + 2), x)
together(1 / (y * x + y) + 1 / (1+x))
cancel(y / (y * x + y))
(x + y).subs(x, y)
expr = sin(x*exp(x))
expr
expr.subs(x, y)
For muliple substitutions, we can pass a dictionary as first and only argument to subs
, which maps old symbols or expressions to new symbols or expressions:
expr = sin(x * z)
expr
expr.subs({sin:cos, x:y, z:exp(x)})
expr = x*y + z**2* x
To substitute numerical values in place of symbolic number, for numerical evaluation, a convenient way of doing this is to define a dictionary that translates the symbols to numerical values, and passing this dictionary as argument to the subs method.
expr
values = {x:1.25,
y:0.04,
z:3.2}
expr.subs(values)
Numerical Evaluation
Even when working with symbolic mathematics, it is almost invariably sooner or later required to evaluate
the symbolic expressions numerically, for example, when producing plots or concrete numerical results.
A SymPy expression can be evaluated using either the sympy.N
function, or the evalf
method of SymPy
expression instances.
Both sympy.N
and the evalf
method take an optional argument that specifies the number of significant
digits to which the expression is to be evaluated.
N(1 + pi)
N(pi, 10)
(x + 1/pi).evalf()
(x + 1/pi).evalf(5)
sympy.lambdify
function takes a set of free symbols and an expression as arguments, and generates a function that efficiently evaluates the numerical value of the expression.
The produced function takes the same number of arguments as the number of free symbols passed as first argument to sympy.lambdify
.
x = symbols('x')
expr = sin(pi*x*exp(x))
expr
expr_num = lambdify(x, expr)
expr_num(10)
By passing the optional argument 'numpy' as third argument to sympy.lambdify
SymPy creates a vectorized function that accepts NumPy arrays as input.
expr_num = lambdify(x, expr, 'numpy')
import numpy as np
xvalues = np.arange(1, 10)
expr_num(xvalues)
x = Symbol('x')
f = Function('f')(x)
diff(f, x)
diff(f, x, x)
diff(f, x, 3)
For multivariate functions -
x, y, z = symbols('x, y, z')
g = Function('g')(x, y)
g.diff(x, y)
g.diff(x, 3, y, 2)
For defined functions -
expr = x**4 + x**3 + x**2 + x + 1
expr.diff(x)
expr.diff(x, x)
expr = (x + 1)**3 * y**2 *(z - 1)
expr.diff(x, y, z)
For trigonometric functions -
expr = sin(x * y) * cos(x / 2)
expr.diff(x)
Alternative way (delayed evaluation) -
expr = exp(cos(x))
d = Derivative(expr, x)
d
d.doit()
a, b, x, y = symbols('a, b, x, y')
f = Function('f')(x)
integrate(f)
integrate(f, (x, a, b))
integrate(sin(x))
integrate(sin(x), (x, a, b))
integrate(exp(-x**2), (x, 0, oo))
SymPy will not be able to give symbolic results for any integral. When SymPy fails to evaluate an integral, an
instance of sympy.Integral
, representing the formal integral, is returned instead.
integrate(sin(x*cos(x)), x)
Multibariable expressions -
integrate(sin(x*exp(y)), x)
expr = (x + y)**2
integrate(expr, x)
By passing more than one symbol, or more than one tuple that contain symbols and their integration limits, we can carry out multiple integration:
expr = (x + y)**2
integrate(expr, (x, 0, 1), (y, 0, 1))
x = Symbol('x')
f = Function('f')(x)
series(f, x)
To change the point around which the function is expanded, we specify $x_0$ argument -
x0 = Symbol('{x_0}')
f.series(x, x0, n=2)
f.series(x, x0, n=2).removeO()
For specified functions -
sin(x).series()
cos(x).series()
exp(x).series()
(1/(1 + x)).series()
For arbitrary expressions of symbols and functions, which in general can also be evaluated.
expr = cos(x)/(1 + sin(x*y))
expr.series(x, n=4)
expr.series(y, n=4)
limit(sin(x)/x, x, 0)
f = Function('f')
x, h = symbols('x, h')
diff_limit = (f(x + h) - f(x))/h
limit(diff_limit.subs(f, cos), h, 0)
limit(diff_limit.subs(f, sin), h, 0)
n = symbols('n', integer=True)
x = Sum(1/n**2, (n, 1, oo))
x
x.doit()
x = Product(n, (n, 1, 7))
x
x.doit()
x = Symbol('x')
solve(x**2 + 2*x -3)
a, b, c, x = symbols('a, b, c, x')
solve(a*x**2 + b*x + c, x)
Trigonometric Functions -
solve(sin(x) - cos(x), x)
Solving a system of equations for more than one unknown variable in SymPy is a straightforward generalization of the procedure used for univariate equations. Instead of passing a single expression as first argument to sympy.solve
, a list of expressions that represent the system of equations is used, and in this case the second argument should be a list of symbols to solve for.
eq1 = x + 2*y -1
eq2 = x - y + 1
solve([eq1, eq2], [x, y], dict=True)
eq1 = x**2 - y
eq2 = y**2 - x
sols = solve([eq1, eq2], [x, y], dict=True)
sols
Verification -
[eq1.subs(sol).simplify() == 0 and eq2.subs(sol).simplify() == 0 for sol in sols]
Matrix([1, 2])
Matrix([ [1, 2 ] ])
Matrix( [[1, 2], [3, 4] ])
Matrix(3, 4, lambda m, n: 10*m + n)
a, b, c, d = symbols('a, b, c, d')
M = Matrix([[a, b], [c, d] ] )
M
M * M
x = Matrix(symbols("x_1, x_2"))
x
M * x
from sympy import *
T, Ta, T0, k, t = symbols('T, T_a, T_0, k, t')
T = Function('T')
diff_eq = Eq(T(t).diff(t),-k*(T(t) - Ta))
diff_eq
sol = dsolve(diff_eq)
sol
ic = Eq(sol.rhs.subs(t, 0), T0)
ic
const = solve(ic)
const
final_sol = sol.subs(const[0])
final_sol
T =lambdify([Ta, T0, k, t], final_sol.rhs)
T
import numpy as np
import matplotlib.pyplot as plt
k = 0.5
Ta = 22
T0 = 100
t = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(t, T(Ta, T0, k, t), color='red', label="""$T_a$ = %0.1f,
$T_0$ = %0.1f,
k = %0.2f"""%(Ta, T0, k))
ax.set_xlabel('Time ($t$) in sec.', fontsize=16)
ax.set_ylabel('Temperature ($T$) in $^{\circ}C$', fontsize=16)
ax.set_title('Cooling Curve', fontsize=20)
# removing top and right spines
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# move bottom and left spine to x = 0 and y = 0
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
ax.set_xticks(range(0, 11, 1))
ax.set_yticks(range(0, 110, 10))
ax.legend(frameon=False)
ax.grid(False)
t, omega0, gamma = symbols('t, omega_0, gamma', positive=True)
x = Function('x')
ode = x(t).diff(t, 2) + 2*gamma*omega0*x(t).diff(t) + omega0**2 *x(t)
ode
Eq(ode)
ode_sol = dsolve(Eq(ode))
ode_sol
ic1 = x(t).subs(t, 0)
ic2 = x(t).diff(t).subs(t, 0)
ics = {ic1:1, ic2:0}
ics
eq1 = Eq(ode_sol.rhs.subs(t, 0), 1)
eq2 = Eq(ode_sol.rhs.diff(t).subs(t, 0), 0)
constants = solve([eq1, eq2])
x_t_sol = ode_sol.subs(constants[0])
x_t_sol
x_t_critical = limit(x_t_sol.rhs, gamma, 1)
x_t_critical
fig, ax = plt.subplots(figsize=(14, 8))
tt = np.linspace(0, 3, 300)
w0 = 2* np.pi
for g in [0.1, 0.5, 1, 2.0, 5.0]:
if g == 1:
x_t = lambdify(t, x_t_critical.subs({omega0:w0, gamma:g}), 'numpy')
else:
x_t = lambdify(t, x_t_sol.rhs.subs({omega0:w0, gamma:g}), 'numpy')
ax.plot(tt, x_t(tt).real, label="$\gamma = %.1f$" % g )
ax.set_xlabel(r"$t$", fontsize=18)
ax.set_ylabel(r"$x(t)$", fontsize=18)
ax.set_title('Damped Harmonic Oscillator', fontsize=20)
# removing top and right spines
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# move bottom and left spine to x = 0 and y = 0
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
ax.set_xticks(np.arange(0, 4, 1))
ax.set_yticks(np.arange(-1, 1.5, 0.5))
ax.legend(frameon=False)
ax.grid(False)
plt.legend(frameon=False, loc='upper right')
plt.grid(False)