SymPy and CAS#

Note

If you don’t have SymPy installed you can try it in your browser with the SymPy Live page

SymPy Introduction#

This page aims to introduce you to the wonderful world of SymPy and CAS (Computer Algebra System). The code examples are based on the SciPy 2016 Conference SymPy tutorial (see More resources on SymPy) and the official documentation for SymPy. The page partly written as an interactive tutorial and you’re encouraged to code along with the provide examples.

SymPy is a Python library for symbolic mathematics [MSP+17]. Some of you may have worked with similar programs such as GeoGebra CAS, Maple, Matlab Symbolic Math Toolbox, Mathematica etc.. These type of programs are often categorized as Computer Algebra Systems or CAS. In general terms, a Computer Algebra System is any software that can manipulate mathematical expressions symbolically. This means that it manipulates expressions similar to how we do. One of the main benefits of this is that there is no loss of precision. An example of this is irrational numbers. Any computer can evaluate the square root of two, but only with limited precision due to hardware constraints. A CAS would express the square root of two as the relationship between the operation “square root” and the integer two. By representing a mathematical expressions as the relationship between operators and numbers we don’t sacrifice any precision.

SymPy Basics#

Importing SymPy#

We can import SymPy using the following convention

import sympy as sm

Since SymPy objects are a bit difficult to interpret, we have methods of printing out mathematical expressions similar to math in a textbook. SymPy supports several printers to output expressions. Using init_printing() will automatically enable the best printer in your environment. This will usually generate an image of the expression you are printing. This webpage, which uses Jupyter notebooks, uses MathJax (a JavaScript library for rendering mathematical notation) to print SymPy expressions.

sm.init_printing(use_latex='mathjax')
x = sm.symbols('x')
x**2
\[\displaystyle x^{2}\]

SymPy Symbols#

Demonstrating the example above, we see that the square root of two has limited precision when using the math.sqrt.

import math
math.sqrt(2)
\[\displaystyle 1.4142135623731\]

Using SymPy we see that the square root of two is expressed symbolically

sm.sqrt(2)
\[\displaystyle \sqrt{2}\]

Computing the square root of 8 demonstrates the real power of SymPy, namely simplification.

sm.sqrt(8)
\[\displaystyle 2 \sqrt{2}\]

Mathematical symbols are created with the symbols() function

x = sm.symbols('x')
x
\[\displaystyle x\]

which creates a symbols object of the Symbol type

type(x)
sympy.core.symbol.Symbol

We can create multiple symbols in one go with symbols(), and Greek symbols spelled out are automatically recognized

alpha, beta, Alpha, Beta = sm.symbols('alpha beta Alpha Beta')
alpha, beta, Alpha, Beta
\[\displaystyle \left( \alpha, \ \beta, \ \mathrm{A}, \ \mathrm{B}\right)\]

The argument in symbols() doesn’t need to match the Python variable name. We can use this to make out Python code more (or less) readable

unrelated = sm.symbols('nonsense')
flywheel_ang_vel, flywheel_inertia = sm.symbols('omega1, I1')
unrelated, flywheel_ang_vel, flywheel_inertia
\[\displaystyle \left( nonsense, \ \omega_{1}, \ I_{1}\right)\]

SymPy has a compact function call to create many similar symbols

sm.symbols('x1:21')
\[\displaystyle \left( x_{1}, \ x_{2}, \ x_{3}, \ x_{4}, \ x_{5}, \ x_{6}, \ x_{7}, \ x_{8}, \ x_{9}, \ x_{10}, \ x_{11}, \ x_{12}, \ x_{13}, \ x_{14}, \ x_{15}, \ x_{16}, \ x_{17}, \ x_{18}, \ x_{19}, \ x_{20}\right)\]

Functions#

We can also define functions in addition to symbols. These are vital when setting up differential equations, where you don’t know the definition of a function, but only its derivative. Using Function() will create a function of the type UndefinedFunction

x = sm.Function('x')
type(x)
sympy.core.function.UndefinedFunction

We can create a function of one of many variables

t = sm.symbols('t')
x(t)
\[\displaystyle x{\left(t \right)}\]

Using the same function…

x(t, alpha, beta)
\[\displaystyle x{\left(t,\alpha,\beta \right)}\]

Exercise

Create a function \(F(t, u)\)

Solution
t, u = sm.symbols('t, u')
F = sm.Function('F')
F(t, u)
\[\displaystyle F{\left(t,u \right)}\]

Symbolic Expressions and Expression Trees#

Using symbolic functions and variables we can construct expressions using mathematical operators.

t, theta = sm.symbols('t, Theta')
x = sm.Function('x')
expr = x(t) - (t**2)/theta
expr
\[\displaystyle x{\left(t \right)} - \frac{t^{2}}{\Theta}\]

Expressions will have a type Add, Mul or Pow. This is because expressions are represented as trees in SymPy. This is important to know when working with SymPy. The internal tree-structure is the reason that SymPy sometimes prints expressions in unusual ways. By using srepr we can see what an expression looks like internally and verify our expressions.

x, y, z = sm.symbols('x, y, z')
expr = x**2  - 2*x*y
expr
\[\displaystyle x^{2} - 2 x y\]

Internal representation:

print(sm.srepr(expr))
Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(-1), Integer(2), Symbol('x'), Symbol('y')))

We can also draw a diagram of the expression tree

digraph{ # Graph style "ordering"="out" "rankdir"="TD" ######### # Nodes # ######### "Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(-2), Symbol('x'), Symbol('y')))_()" ["color"="black", "label"="Add", "shape"="ellipse"]; "Pow(Symbol('x'), Integer(2))_(0,)" ["color"="black", "label"="Pow", "shape"="ellipse"]; "Symbol('x')_(0, 0)" ["color"="black", "label"="x", "shape"="ellipse"]; "Integer(2)_(0, 1)" ["color"="black", "label"="2", "shape"="ellipse"]; "Mul(Integer(-2), Symbol('x'), Symbol('y'))_(1,)" ["color"="black", "label"="Mul", "shape"="ellipse"]; "Integer(-2)_(1, 0)" ["color"="black", "label"="-2", "shape"="ellipse"]; "Symbol('x')_(1, 1)" ["color"="black", "label"="x", "shape"="ellipse"]; "Symbol('y')_(1, 2)" ["color"="black", "label"="y", "shape"="ellipse"]; ######### # Edges # ######### "Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(-2), Symbol('x'), Symbol('y')))_()" -> "Pow(Symbol('x'), Integer(2))_(0,)"; "Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(-2), Symbol('x'), Symbol('y')))_()" -> "Mul(Integer(-2), Symbol('x'), Symbol('y'))_(1,)"; "Pow(Symbol('x'), Integer(2))_(0,)" -> "Symbol('x')_(0, 0)"; "Pow(Symbol('x'), Integer(2))_(0,)" -> "Integer(2)_(0, 1)"; "Mul(Integer(-2), Symbol('x'), Symbol('y'))_(1,)" -> "Integer(-2)_(1, 0)"; "Mul(Integer(-2), Symbol('x'), Symbol('y'))_(1,)" -> "Symbol('x')_(1, 1)"; "Mul(Integer(-2), Symbol('x'), Symbol('y'))_(1,)" -> "Symbol('y')_(1, 2)"; }

Note

The diagram above was generated by using Graphviz and dotprint

Notice how the nodes in the tree are structured according to the order of operations. The operations are defined as classes in SymPy, and we could just as easily define our expressions using Add, Mul, Pow, Symbol (Add, Multipy, Power, Symbol). Let’s look at a simpler expression: \(x^2\)

x = sm.symbols('x')
expr = x**2
sm.srepr(expr)
"Pow(Symbol('x'), Integer(2))"

digraph{ # Graph style "ordering"="out" "rankdir"="TD" ######### # Nodes # ######### "Pow(Symbol('x'), Integer(2))_()" ["color"="black", "label"="Pow", "shape"="ellipse"]; "Symbol('x')_(0,)" ["color"="black", "label"="x", "shape"="ellipse"]; "Integer(2)_(1,)" ["color"="black", "label"="2", "shape"="ellipse"]; ######### # Edges # ######### "Pow(Symbol('x'), Integer(2))_()" -> "Symbol('x')_(0,)"; "Pow(Symbol('x'), Integer(2))_()" -> "Integer(2)_(1,)"; }

By using the same operators in the graph we can create the same object.

expr = sm.Pow(sm.Symbol('x'), sm.Integer(2))
expr
\[\displaystyle x^{2}\]

See the manipulation section of the official SymPy tutorial for more information on this topic.

SymPy has a comprehensive library of functions, all of which are documented in the official documentation.

expr2 = sm.sqrt(x)*sm.sin(x) + sm.Abs(z)/y
expr2
\[\displaystyle \sqrt{x} \sin{\left(x \right)} + \frac{\left|{z}\right|}{y}\]

When working with fractions, keep in mind that SymPy may evaluate the expression. We can get around this by using S() to sympify numbers. This is especially useful when working with irrational numbers

1/3 * x
\[\displaystyle 0.333333333333333 x\]
sm.S(1)/3 * x
\[\displaystyle \frac{x}{3}\]

Exercise

Create a SymPy expression for the normal distribution function

(1)#\[\frac{1}{\sqrt{2\pi\sigma}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}\]
Solution
x, mu, sigma = sm.symbols('x mu sigma')
normal = 1/sm.sqrt(2 * sm.pi * sigma**2)* sm.exp(-(x - mu)**2/(2*sigma**2))
normal
\[\displaystyle \frac{\sqrt{2} e^{- \frac{\left(- \mu + x\right)^{2}}{2 \sigma^{2}}}}{2 \sqrt{\pi} \sqrt{\sigma^{2}}}\]

Printing and Sympification#

As illustrated with srepr() above, expressions in SymPy can have many representations. The most standard representation is str(), which gives a representation similar to what you would code

expr3 = x*sm.cos(x)**2/sm.Abs(sm.Symbol('omega'))
str(expr3)
'x*cos(x)**2/Abs(omega)'

SymPy also has a pretty printer pprint() that prints unicode symbols more similar to the typesetting we are used to

sm.pprint(expr3)
     2   
x⋅cos (x)
─────────
   │ω│   

Additionally, SymPy can also generate LaTeX representations of SymPy expressions using the sympy.printing.latex.latex() function

print(sm.latex(expr3))
\frac{x \cos^{2}{\left(x \right)}}{\left|{\omega}\right|}

SymPy can also interpret and convert strings into SymPy expressions

s = sm.sympify('x*cos(x)/omega')
s
\[\displaystyle \frac{x \cos{\left(x \right)}}{\omega}\]

It’s also worth noting that SymPy can generate expressions in many different programming languages. This allows us to use SymPy to solve/find the expressions we want, which we then implement in our programs

print(sm.ccode(expr3))          # C
print(sm.fcode(expr3))          # Fortran
print(sm.rust_code(expr3))      # Rust
print(sm.octave_code(expr3))    # Matlab / Octave
print(sm.julia_code(expr3))     # Julia
# LLVM etc ...
x*pow(cos(x), 2)/fabs(omega)
      x*cos(x)**2/abs(omega)
x*x.cos().powi(2)/omega.abs()
x.*cos(x).^2./abs(omega)
x .* cos(x) .^ 2 ./ abs(omega)

Exercise

Use the latex method demonstrated in the SymPy documentation to generate a LaTex expression for the normal distribution.

Solution
print(sm.latex(normal))
\frac{\sqrt{2} e^{- \frac{\left(- \mu + x\right)^{2}}{2 \sigma^{2}}}}{2 \sqrt{\pi} \sqrt{\sigma^{2}}}

Differentiation#

Note

SymPy has several methods for computing an integral. Since most of the systems we’ll work with in the course don’t have analytical solutions, we won’t introduce them here. See the the calculus section of the official SymPy tutorial if you want to learn more.

Computing derivatives of complex expressions by hand can be very tedious process prone to errors. With SymPy we can calculate derivatives with ease. All functions and expressions have a .diff() method which can be used to differentiate. There is also a standalone function diff() which takes a undefined function or an expression and differentiates it with respect to the second argument. This works irrespective of dimension, given that the corresponding arguments are correct.

f = sm.Function('f')
f(t).diff(t)
\[\displaystyle \frac{d}{d t} f{\left(t \right)}\]
sm.diff(f(x), x)
\[\displaystyle \frac{d}{d x} f{\left(x \right)}\]

Let’s say we have some complicated expression

expr4 = sm.Abs(x)*sm.sin(t)**2/x
expr4
\[\displaystyle \frac{\sin^{2}{\left(t \right)} \left|{x}\right|}{x}\]

We can express the derivative of expr4 with respect to \(x\) and then \(t\) by using Derivative()

sm.Derivative(expr4, x, t)
\[\displaystyle \frac{\partial^{2}}{\partial t\partial x} \frac{\sin^{2}{\left(t \right)} \left|{x}\right|}{x}\]

We can compute the derivative with the method doit(), which is the as expr4.diff(args)

sm.Derivative(expr4, x, t).doit()
\[\displaystyle \frac{2 \left(\left(\operatorname{re}{\left(x\right)} \frac{d}{d x} \operatorname{re}{\left(x\right)} + \operatorname{im}{\left(x\right)} \frac{d}{d x} \operatorname{im}{\left(x\right)}\right) \operatorname{sign}{\left(x \right)} - \left|{x}\right|\right) \sin{\left(t \right)} \cos{\left(t \right)}}{x^{2}}\]
expr4.diff(x, t)
\[\displaystyle \frac{2 \left(\left(\operatorname{re}{\left(x\right)} \frac{d}{d x} \operatorname{re}{\left(x\right)} + \operatorname{im}{\left(x\right)} \frac{d}{d x} \operatorname{im}{\left(x\right)}\right) \operatorname{sign}{\left(x \right)} - \left|{x}\right|\right) \sin{\left(t \right)} \cos{\left(t \right)}}{x^{2}}\]

Note that the derivative includes both real and imaginary components. This is intentional.

Warning

SymPy assumes that all symbols are complex-valued unless it is given additional assumptions. We can attach assumptions to a symbol or function to specify if they are real, positive, negative etc.

s = sm.symbols('s')
H = sm.Function('H')
sm.Abs(H(s)).diff(s)
\[\displaystyle \frac{\left(\operatorname{re}{\left(H{\left(s \right)}\right)} \frac{d}{d s} \operatorname{re}{\left(H{\left(s \right)}\right)} + \operatorname{im}{\left(H{\left(s \right)}\right)} \frac{d}{d s} \operatorname{im}{\left(H{\left(s \right)}\right)}\right) \operatorname{sign}{\left(H{\left(s \right)} \right)}}{H{\left(s \right)}}\]
H = sm.Function('H', real=True)
sm.Abs(H(s)).diff(s)
\[\displaystyle \operatorname{sign}{\left(H{\left(s \right)} \right)} \frac{d}{d s} H{\left(s \right)}\]
H = sm.Function('H', real=True, positive=True)
sm.Abs(H(s)).diff(s)
\[\displaystyle \frac{d}{d s} H{\left(s \right)}\]

In most cases, adding assumptions to variables isn’t necessary, but it can be useful when you encounter unexpected components in your solutions.

Exercise

Demonstrate the chain rule by differentiating \(f(g(x))\) with respect to \(x\) using SymPy

Solution
f(sm.Function('g')(x)).diff(x)
\[\displaystyle \frac{d}{d g{\left(x \right)}} f{\left(g{\left(x \right)} \right)} \frac{d}{d x} g{\left(x \right)}\]

Substitution and Evaluation#

SymPy has many methods for evaluating expressions numerically, and replace() is often used. In this course we prefer xreplace() for its verbosity. We first create a dictionary to map the symbols or expressions we want to substitute, and then pass it to xreplace()

repl = {x: sm.sqrt(2), t: sm.pi/7}
expr4.xreplace(repl)
\[\displaystyle \sin^{2}{\left(\frac{\pi}{7} \right)}\]

SymPy doesn’t evaluate the expression automatically after substituting. We can use the evalf() method to evaluate the expression to a specified number of decimal points given a dictionary with substitutions.

expr4.evalf(n = 10, subs = repl)
\[\displaystyle 0.1882550991\]

We can do this because evalf() returns a special SymPy Float object which can have an arbitrary number of decimal places. Here we evaluate pi at 1000 decimal places

pi_e3 =  sm.pi.evalf(n = 1000)
pi_e3
\[\displaystyle 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420199\]

If you want a regular machine precision floating point value you can easily convert a SymPy float to a Python float

type(float(pi_e3))
float

When we want to evaluate an expression with machine precision directly, we prefer to use lambdify() to convert the expression into a Python function. We can convert an expression by providing the symbols that should be converted into numbers as a tuple. We can then use help() to inspect our lambdified expression.

eval_expr4 = sm.lambdify((x, t), expr4)
help(eval_expr4)
Help on function _lambdifygenerated:

_lambdifygenerated(x, t)
    Created with lambdify. Signature:
    
    func(x, t)
    
    Expression:
    
    sin(t)**2*Abs(x)/x
    
    Source code:
    
    def _lambdifygenerated(x, t):
        return sin(t)**2*abs(x)/x
    
    
    Imported modules:

The lambdified function works as any other Python function. Note that it returns NumPy floats instead of Python floats. These can be used with Python floats interchangeably, but neither should be mixed with SymPy floats. We prefer the much faster NumPy floats since the arbitrary precision of a Python float isn’t required. We will almost always want machine precision floats, so lambdify() is your friend.

type(eval_expr4(1,2))
numpy.float64

If you want a quick plot without evaluating your expression, you can use plot()

sm.plot(sm.sin(x)**2)
_images/sympy-and-cas_45_0.png
<sympy.plotting.plot.Plot at 0x7f10c4f2dd50>

Matrices#

Matrices can be creating by passing a nested list to the Matrix() object

A = sm.Matrix([[0.2, - 1], [0, 0.9]])
A
\[\begin{split}\displaystyle \left[\begin{matrix}0.2 & -1\\0 & 0.9\end{matrix}\right]\end{split}\]
a, b, c, d = sm.symbols('a, b, c, d')
B = sm.Matrix([[1/b, 1/b], [c/d, 1/a]])
B
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{1}{b} & \frac{1}{b}\\\frac{c}{d} & \frac{1}{a}\end{matrix}\right]\end{split}\]

We can access individual elements of a matrix with square brackets

B[1,0]
\[\displaystyle \frac{c}{d}\]

We can also use slice notation to extract rows or columns

B[0:2, 1]
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{1}{b}\\\frac{1}{a}\end{matrix}\right]\end{split}\]
B[0, 0:2]
\[\displaystyle \left[\begin{matrix}\frac{1}{b} & \frac{1}{b}\end{matrix}\right]\]

A list of elements is interpreted as a column vector

C = sm.Matrix([a, b])

We can get the shape of a matrix by using the shape() function or the shape attribute

print(sm.shape(C))
print(C.shape)
(2, 1)
(2, 1)

There are many methods for creating common matrices

sm.eye(4,4)
\[\begin{split}\displaystyle \left[\begin{matrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]\end{split}\]
sm.zeros(3,1)
\[\begin{split}\displaystyle \left[\begin{matrix}0\\0\\0\end{matrix}\right]\end{split}\]
sm.ones(6,4)
\[\begin{split}\displaystyle \left[\begin{matrix}1 & 1 & 1 & 1\\1 & 1 & 1 & 1\\1 & 1 & 1 & 1\\1 & 1 & 1 & 1\\1 & 1 & 1 & 1\\1 & 1 & 1 & 1\end{matrix}\right]\end{split}\]

For creating matrices with elements or matrices on the diagonal we use diag()

sm.diag(a, b, c, sm.ones(6,4))
\[\begin{split}\displaystyle \left[\begin{matrix}a & 0 & 0 & 0 & 0 & 0 & 0\\0 & b & 0 & 0 & 0 & 0 & 0\\0 & 0 & c & 0 & 0 & 0 & 0\\0 & 0 & 0 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1\end{matrix}\right]\end{split}\]

To transpose a matrix we can use the attribute .T

sm.diag(a, b, c, sm.ones(6,4)).T
\[\begin{split}\displaystyle \left[\begin{matrix}a & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & b & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & c & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 1\\0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 1\end{matrix}\right]\end{split}\]

We can easily perform matrix algebra

B + B
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{2}{b} & \frac{2}{b}\\\frac{2 c}{d} & \frac{2}{a}\end{matrix}\right]\end{split}\]

We can use both * and @ to perform matrix multiplication. Since NumPy uses * for element-wise multiplication and @ for matrix multiplication, it’s best to use @ for SymPy matrix multiplication to avoid any confusion

B*C
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{a}{b} + 1\\\frac{a c}{d} + \frac{b}{a}\end{matrix}\right]\end{split}\]
B@C
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{a}{b} + 1\\\frac{a c}{d} + \frac{b}{a}\end{matrix}\right]\end{split}\]

For element-wise multiplication:

sm.hadamard_product(B,B)
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{1}{b^{2}} & \frac{1}{b^{2}}\\\frac{c^{2}}{d^{2}} & \frac{1}{a^{2}}\end{matrix}\right]\end{split}\]

Useful attributes, such as the determinant, eigenvalues, eigenvectors and singular values are implemented as matrix methods

r, theta = sm.symbols('r, theta', real=True)
rot = sm.Matrix([[r*sm.cos(theta), -r * sm.sin(theta)],[r*sm.sin(theta), r*sm.cos(theta)]])
rot
\[\begin{split}\displaystyle \left[\begin{matrix}r \cos{\left(\theta \right)} & - r \sin{\left(\theta \right)}\\r \sin{\left(\theta \right)} & r \cos{\left(\theta \right)}\end{matrix}\right]\end{split}\]
rot.det()
\[\displaystyle r^{2} \sin^{2}{\left(\theta \right)} + r^{2} \cos^{2}{\left(\theta \right)}\]
rot.singular_values()
\[\displaystyle \left[ \left|{r}\right|, \ \left|{r}\right|\right]\]
D = sm.Matrix([[1, x], [y, 1]])
D
\[\begin{split}\displaystyle \left[\begin{matrix}1 & x\\y & 1\end{matrix}\right]\end{split}\]
D.eigenvals()
\[\displaystyle \left\{ 1 - \sqrt{x y} : 1, \ \sqrt{x y} + 1 : 1\right\}\]
D.eigenvects()
\[\begin{split}\displaystyle \left[ \left( 1 - \sqrt{x y}, \ 1, \ \left[ \left[\begin{matrix}\frac{1 - \sqrt{x y}}{y} - \frac{1}{y}\\1\end{matrix}\right]\right]\right), \ \left( \sqrt{x y} + 1, \ 1, \ \left[ \left[\begin{matrix}\frac{\sqrt{x y} + 1}{y} - \frac{1}{y}\\1\end{matrix}\right]\right]\right)\right]\end{split}\]

Matrices are mutable, meaning that you can change them in place. This means they cannot be used inside other SymPy expressions or as keys to dictionaries. If needed, there is an immutable version of a matrix in SymPy called ImmutableMatrix

C *=2
C += sm.Matrix([0, 1])
C[0] = b
C
\[\begin{split}\displaystyle \left[\begin{matrix}b\\2 b + 1\end{matrix}\right]\end{split}\]

We can differentiate a matrix by .diff()

C.diff(b)
\[\begin{split}\displaystyle \left[\begin{matrix}1\\2\end{matrix}\right]\end{split}\]

We can also calculate the Jacobian of a vector with jacobian()

C.jacobian([a, b])
\[\begin{split}\displaystyle \left[\begin{matrix}0 & 1\\0 & 2\end{matrix}\right]\end{split}\]

Exercise

The nonlinear state space model for an inverted pendulum is given below. Use the jacobian() message to linearize the system around the equilibrium \(x_0 = \begin{bmatrix} 0 \\ 0 \end{bmatrix}\)

import sympy as sm

x1, x2 = sm.symbols('x1 x2')
u = sm.symbols('u')
g, l, m = sm.symbols('g l m')

x = sm.Matrix([x1, x2])

f1 = x2
f2 = -(g/l)*sm.sin(x1) + (1/(m*l**2))*u
f = sm.Matrix([f1, f2])
f
\[\begin{split}\displaystyle \left[\begin{matrix}x_{2}\\- \frac{g \sin{\left(x_{1} \right)}}{l} + \frac{u}{l^{2} m}\end{matrix}\right]\end{split}\]
Solution
A = f.jacobian(x)
B = f.jacobian(sm.Matrix([u]))

# Define the linearization point around equilibrium
x_eq = sm.Matrix([0, 0])
u_eq = 0

subs = {x1: x_eq[0], x2: x_eq[1], u: u_eq}

A_lin = A.xreplace(subs)
B_lin = B.xreplace(subs)

A_lin@x + B_lin*u
\[\begin{split}\displaystyle \left[\begin{matrix}x_{2}\\- \frac{g x_{1}}{l} + \frac{u}{l^{2} m}\end{matrix}\right]\end{split}\]

Alternatively, the small-angle approximation will yield the same result.

Linear Systems#

SymPy has many ways of solving Matrix equations on the form \(\bf{A}x = b\). The best method depends on the nature of the matrix. By default, Gauss-Jordan elimination will be used, which can be quite inefficient for large matrices. If you repeatedly need to solve matrix equations with the same matrix, it is usually faster to use LU decomposition via the LUsolve method

A = sm.Matrix([[c, d],[1, -a]])
b = sm.Matrix([3, 0])
solution = A.LUsolve(b)
solution
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{3 a}{a c + d}\\\frac{3}{a c + d}\end{matrix}\right]\end{split}\]

We can verify the solution

sm.simplify(A@solution)
\[\begin{split}\displaystyle \left[\begin{matrix}3\\0\end{matrix}\right]\end{split}\]

We could also find the inverse of the matrix \(A\), but this is usually significantly slower, especially with large matrices.

A_inv = A.inv()
solution = A_inv@b
solution
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{3 a}{a c + d}\\\frac{3}{a c + d}\end{matrix}\right]\end{split}\]

Equations with no solution will return an error

Simplification#

When working with dynamical systems the computation of Jacobians and matrix inversion will inevitably lead to large and complicated expressions. Luckily, SymPy has a function for automatically simplifying symbolic expressions, namely simplify().

a, t = sm.symbols('a t')
expr6 = sm.Matrix([sm.cos(a)/t + t*sm.sin(t) + 100, sm.cos(a) + t*a])
A = expr6.jacobian([a, t]).inv()
sm.simplify(A)
\[\begin{split}\displaystyle \left[\begin{matrix}- \frac{a t^{2}}{a t \sin{\left(a \right)} + t^{4} \cos{\left(t \right)} - t^{3} \sin{\left(a \right)} \cos{\left(t \right)} + t^{3} \sin{\left(t \right)} - t^{2} \sin{\left(a \right)} \sin{\left(t \right)} - t \cos{\left(a \right)} + \frac{\sin{\left(2 a \right)}}{2}} & \frac{t^{3} \cos{\left(t \right)} + t^{2} \sin{\left(t \right)} - \cos{\left(a \right)}}{a t \sin{\left(a \right)} + t^{4} \cos{\left(t \right)} - t^{3} \sin{\left(a \right)} \cos{\left(t \right)} + t^{3} \sin{\left(t \right)} - t^{2} \sin{\left(a \right)} \sin{\left(t \right)} - t \cos{\left(a \right)} + \frac{\sin{\left(2 a \right)}}{2}}\\\frac{t^{2} \left(t - \sin{\left(a \right)}\right)}{a t \sin{\left(a \right)} + t^{4} \cos{\left(t \right)} - t^{3} \sin{\left(a \right)} \cos{\left(t \right)} + t^{3} \sin{\left(t \right)} - t^{2} \sin{\left(a \right)} \sin{\left(t \right)} - t \cos{\left(a \right)} + \frac{\sin{\left(2 a \right)}}{2}} & \frac{t \sin{\left(a \right)}}{a t \sin{\left(a \right)} + t^{4} \cos{\left(t \right)} - t^{3} \sin{\left(a \right)} \cos{\left(t \right)} + t^{3} \sin{\left(t \right)} - t^{2} \sin{\left(a \right)} \sin{\left(t \right)} - t \cos{\left(a \right)} + \frac{\sin{\left(2 a \right)}}{2}}\end{matrix}\right]\end{split}\]

Simplifying very large expressions usually won’t give you a better result. Simplifying specific parts of your expressions can sometimes yield better results. The trigsimp() function tries to find a simpler trigonometric expression

sm.trigsimp(sm.cos(t)**2 + sm.sin(t)**2)
\[\displaystyle 1\]

More resources on SymPy#

It’s highly recommended that you familiarize yourself with the SymPy documentation, available though this link here. The documentation page on common mistakes and “gochas” is particularty useful. An older long-form version of this SymPy tutorial is available on YouTube.