# SymPy: Symbolic Python

## Video

## Motivation

Mathematicians tend to work with symbols more than data. While we are often manipulating them on paper, computers can manipulate them for us in many situations.

In addition, arbitrary position integers, polynomials, more general group elements like modular integers or elliptic curve points, and other similar arithmetic constructs can often be manipulated by computers for us with a bit of symbolic magic.

There are two different Python symbolic computation packages with two starkly different philosophies and separate sets of higher level capabilites. For the simple stuff, they are fairly interchangeable - but depending on your domain, **Sage** may be the right choice for you.

**Sympy** tends to be easier to use and learn, however, so we will be *teaching* it.

## Core Object(s)

### Expressions

**Sympy**'s core object is the *expression*. Like in **Numpy**, they are typically *built* rather than passed to an explicit constructor.

The simplest kind of expression is the *symbol*. **Sympy** has a quick interface to symbols for upper and lowercase roman and greek letters:

```
import sympy
from sympy.abc import x
example_poly = x**2-1
example_poly
```

Symbols can also be constructed explicitly, if you need longer ones or custom renders:

```
x1,x2 = sympy.symbols("x_1 x_2")
x1
```

From symbols, together with the arithmetic operators and functions like `sympy.sin`

, it is possible to construct complicated expressions:

```
expr = 1+sympy.sin(x)**2/sympy.cos(x)**2
expr
```

One neat thing you can do with **Sympy** is simplify expressions:

```
sympy.simplify(expr)
```

Note that **Sympy** can automatically format pretty-printed output for us!

We can get that output as $\LaTeX$:

```
print(sympy.latex(expr))
```

This can be a real boon for accurately constructing and representing objects in your research.

#### Evaluation

**SymPy** expressions, even when they contain no variables, are symbolic:

```
sympy.sqrt(2)
```

This is really useful, but sometimes we want numerical values (like for plotting!)

Expressions support the `.n()`

method for numerical approximations:

```
a = sympy.sqrt(2)
a.n()
```

If you want to evaluate at variables, you can substitute expressions for specific values:

```
from sympy.abc import x,y
expr = (sympy.sin(x)+sympy.cos(y))
expr.subs({x:x**2})
expr.subs({x:1,y:2}).n()
```

#### Parsing

Instead of *building* expressions from their base components - a very reliable method - you can also *parse* input.

```
sympy.parse_expr("sin(x**2+y)")
```

This is commonly used in programs for taking in user data, and can be a quick way to parse input.

The standard form is the "math string" we are by now largely comfortable with, but it can also (sometimes) parse other formats, like $\LaTeX$. (there are philosophical differences that make such parsing... difficult.)

### Matrices

Different **Sympy** domains revolve around different constructs; for example, the **Linear Algebra** domain revolves around the `sympy.Matrix`

object:

```
sympy.Matrix([[1,1],[1,0]])
```

With these, we can finally realize the absurdly fast Fibonacci algorithm we glimpsed with **Numpy** for arbitrary precision integers:

```
def fibonacci(n):
if n<1:
return n
iterator =sympy.Matrix([[1,1],[1,0]])
base_case = sympy.Matrix([1,1])
return (iterator**(n-1)*base_case)[0]
fibonacci(10**5)
```

**Sympy** `Matrix`

es are *not* like `ndarray`

s; they respond to all our functions and operators as a mathematician would expect a `Matrix`

to; Because they contain Python objects, they can't take advantage of the same parallel computations as `Numpy`

, so their speed relies on the work of linear algebraists, number theorists, and computer scientists - together with the inherent power of the matrix.

Because their notation is more familiar, there is a bit less to go over - but there are some special constructors worth talking about. Note that **Sympy** - like mathematicians - is mostly concerned with the creation and manipulation of *square* matrices.

Command | Example | TeX | Notes |
---|---|---|---|

`sympy.Matrix(iterable)` |
`sympy.Matrix([0,1,2])` |
$\begin{bmatrix}0\\1\\2\end{bmatrix}$ | Constructs Column Vector matrix from 1d positioned data |

`sympy.Matrix(iterable_of_iterables)` |
`sympy.Matrix([[0,1,2],[3,4,5]])` |
$\begin{bmatrix}0&1&2\\3&4&5\end{bmatrix}$ | Constructs from 2d positioned data |

`sympy.Matrix(rows, columns, iterable)` |
`sympy.Matrix(2,3,range(6))` |
$\begin{bmatrix}0&1&2\\3&4&5\end{bmatrix}$ | Constructs from dimensions, and a 1d list |

`sympy.eye(dim)` |
`sympy.eye(2)` |
$\begin{bmatrix}1&0\\0&1\end{bmatrix}$ | |

`sympy.zeros(dim)` |
`sympy.zeros(2)` |
$\begin{bmatrix}0&0\\0&0\end{bmatrix}$ | |

`sympy.ones(dim)` |
`sympy.ones(2)` |
$\begin{bmatrix}1&1\\1&1\end{bmatrix}$ | |

`sympy.matrices.diag([1,2])` |
|||

`Matrix.permute(permutation,orientation="rows")` |
`sympy.eye(3).permute([2,0,1])` |
$\begin{bmatrix}0&0&1\\1&0&0\\0&1&0\end{bmatrix}$ | Can permute rows or columns - is great for creating permutation matrices |

`sympy.eye(dim)` |
`sympy.eye(2)` |
$\begin{bmatrix}1&0\\0&1\end{bmatrix}$ |

Block construction ends up being a bit finnicky in **SymPy**:

```
A = sympy.zeros(3)
B = sympy.ones(3,2)
C = 2*sympy.ones(2,3)
D = sympy.eye(2)
M_blocks = sympy.BlockMatrix([[A,B],[C,D]])
M = sympy.Matrix(M_blocks)
N_blocks = sympy.BlockDiagMatrix(A,B,C,D)
N = sympy.Matrix(N_blocks)
```

## Modules

**SymPy** functionality is largely split into various **Modules** - that is, python *submodules* - which you can read about in the documentation. As of writing, it is really good at:

- polynomials
- trigonometry
- integral and differential calculus
- Quadratic extensions of $\mathbb R$ and $\mathbb Q$, such as the complex numbers (and their extensions)
- Real and complex symbolic linear algebra
- Parsing and simplifying (reasonably simple) expressions

And experimental functionality in a variety of other areas.

If you need symbolic computations of other sorts, I highly encourage you to explore Sage, a truly incredible feat of software stitching. Sage attempts to perform interfaces and interoperability to *almost everything mathematicians use* - allowing you to take objects out of **Macaulay** and into **Matlab** or **Mathematica** seamlessly. It could be the subject of its own course - and tends to be relatively complicated to install, operate, and configure.

### Polynomials

Polynomial coefficients have a way of getting away from you. When I need polynomial expansions, I tend to do some quick sympy `expr.expand()`

to get the computer to do it for me.

However, we all know that polynomials have a lot more going on.

Consider the following polynomial, which we know has a algebraic solution:

```
from sympy.abc import x
polynomial = x**3+3*x**2+6*x+4
roots = sympy.solve(polynomial)
sympy.factor(polynomial)
roots
```

We can set some reasonable assumptions on our values, to get restricted answers:

```
a = sympy.symbols("a",real=True)
polynomial = a**3+3*a**2+6*a+4
sympy.solve(polynomial)
b = sympy.symbols("b",positive=True)
polynomial = b**3+3*b**2+6*b+4
sympy.solve(polynomial)
```

Sympy can solve in terms of various variables:

```
expr = y**2-x**3-x-1
sympy.solve(expr,y)
sympy.solve(expr,x)
```

And even do polynomial long division and gcd:

```
expr1 = x**3-2*x-x+2
expr2 = x**2+4*x+4
sympy.gcd(expr1,expr2)
sympy.factor(expr1)
sympy.poly(expr1).div(sympy.poly(expr2))
```

Note that several of the fancier polynomial tricks are hiding various places, like in the `sympy.poly`

object and `sympy.polys`

submodule.

### Trigonometry

**Sympy** has some fairly efficient tools for symbolic trigonometry:

```
expr = sympy.sin(x+sympy.pi/3)+sympy.sin(x)
sympy.simplify(expr)
str(expr)
```

It can solve these equations, even as part of many polynomials:

```
from sympy.abc import theta
expr = sympy.sin(theta)+sympy.cos(theta)**2+1
solutions = sympy.solve(expr)
expr.subs(theta, solutions[0])
```

However, it might need to be told to think little deeper when substituting:

```
evaluated = expr.subs(theta, solutions[1])
sympy.simplify(evaluated)
```

It favors simplifying into as few $\sin$ and $\cos$ terms as it can, and changing other trig functions into those:

```
expr = sympy.tan(x)**2+sympy.sec(x)**2
sympy.simplify(expr)
```

### Integral and Differential Calculus

**SymPy** is very good at integral and differential calculus, though it doesn't know the scientific names of beings animalculous.

With symbolic solvers for differentiation, integration, and many ODEs and PDEs in several variables, it is a great resource - just make sure you sanity-check the answers you get.

To take the derivative of a simple expression, do:

```
from sympy.abc import x
expr = x**3-sympy.sin(x)*x
expr.diff()
```

Or, for multivariate,

```
from sympy.abc import x,y
expr = x**3-sympy.sin(y)*x
expr.diff(y)
expr.diff(x)
```

With a similar syntax for simple integrals:

```
expr.integrate(x)
expr.integrate(y)
```

Note the lack of a `+C`

constant; to get constants, add them in yourself or get the general solution to a simple differential equation, which is more complete.

#### Differential Equations

We won't get into this much here, but there are many solvers for differential equation systems.

As a simple example,

```
f,g = sympy.symbols('f g',cls=sympy.Function)
expr = f(x).diff(x,x)-2*f(x).diff(x) + 1
```

We can now solve the differential equation to get a definition for $f$:

```
eqn = sympy.dsolve(expr,f(x))
```

And extract the solution as an *expression*, and its newly defined symbols;

```
solution = eqn.rhs
C1,C2,x = solution.free_symbols
```

### Quadratic Extensions

The quadratic field we are all most familiar with is the Gaussian Rationals; for those, we can use `sympy.I`

as the imaginary constant;

```
a = 1+sympy.I
abs(a)
```

For other quadratic fields, we can construct them either explicitly - with `sympy.sqrt`

- or by minimal polynomial:

```
poly = x**2-x-1
a = sympy.solve(poly,x)[0]
b = (2*a-3)**2
```

We may sometimes need to explicitly expand:

```
b.expand()
```

but other than that they behave almost entirely as expected.

Sympy also works fairly well for other extensions by $n$th roots, and extensions of extensions, but (understandably) struggles somewhat on explicit computations in more general algebraic extensions.

### Linear Algebra

We have already seen constructing matrices, and can imagine multiplying and adding them.

In **SymPy**, most of the more common matrix operations - standard decompositions, and so on - are stored as properties of the `Matrix`

class:

```
M = sympy.Matrix([[1,2],[3,4]])
M.QRdecomposition()
```

To get a full picture of the available methods, you can look through `dir(M)`

on a matrix. The names have been chosen to try to make it easy for mathematicians to understand what the functions do.

Note that you can do symbolic math *on* matrices - either matrices of symbols:

```
a_11,a_12,a_21,a_22 =sympy.symbols("a_{1\,1} a_{1\,2} a_{2\,1} a_{2\,2}")
M = sympy.Matrix([[a_11,a_12],[a_21,a_22]])
M.det()
```

Or matrices that *are* symbols:

```
N = sympy.MatrixSymbol('N',2,2)
M*N*M.inv()
```

### Parsing

We will discuss parsing more next week, but play around with `sympy.parse`

to get a feel for how your representations of math translate.

## Worksheet

Today's worksheet has you solving a classic related rates problem with Sympy - using its trigonometry, calculus, and solving functionality.