# Math 300: Python Symbolics (back to Math 300 notes)

In the twenty-first century there is little or no reason to compute complicated integrals and derivatives by hand. We can instead use any of various python packages to do exact arithmetic, perform algebraic operations, and evaluate limits, integrals and other calculus constructions easily. One package for python that is somewhat mature is called Sage. This depends upon python, but builds on top of it, rather than being a simple package such as numpy. We will instead use sympy, a relatively new and less than mature package with a bright future. Ultimately, the point is that symbolic math packages are a dime a dozen these days, and learning one will demonstrate most of the ideas anchoring all of them, though the details of syntax vary from one package to another.

The first thing to note about sympy is that it prefers to treat numbers as exact. This means that each number is an object in itself, with various means of interpretation, including approximation to an arbitrary number of digits of accuracy. Thus, numpy provides a fixed floating point approximation to π, but sympy treats π as a real number that can be evaluated in floating point arithmetic to any number of digits you require.

>>> from numpy import * >>> pi 3.141592653589793 >>> from sympy import * >>> pi pi >>> pi.evalf() 3.14159265358979 >>> pi.evalf(50) 3.1415926535897932384626433832795028841971693993751

There are several significant things here. First, when you do arithmetic with sympy objects you might not get the simple numbers you expect - learn to change your expectations. The second thing is that working with symbolic objects will be much slower than working with ordinary python numbers. Do not use sympy objects unless you really need to do exact or symbolic calculations.

Another thing to be careful with here is that if it is not clear that the object we are dealing with is a sympy object, then ordinary python rules apply. Thus, 1/3 is treated as an integer arithmetic operation, with result 0. Likewise, 1.0/3 is treated as a floating point operation, with result 0.3333333333333333. However, if we want to do an exact calculation, we must make a rational expression. As soon as python realizes it is working with one rational, it treats an entire expression as if it is rational.

>>> q=Rational(1,3) >>> q 1/3 >>> q.evalf(50) 0.33333333333333333333333333333333333333333333333333 >>> q=Rational(1)/3 >>> q 1/3

The whole point of working with a symbolic package is to permit us to do calculations with symbolic variables. Sympy allows us to define symbolic variables and then work with them. There are two ways to start this: we can use the symbols command, or we can use the var command. New variables formed using symbolic variables are themselves symbolic.

>>> x,y=symbols('x,y') >>> var('u,v') (u, v) >>> x-x 0 >>> x+x 2*x >>> expr=(u+v)**4 >>> expr (u + v)**4

Symbolic variables have many functions associated with them that allow us to do various algebraic and calculus operations. The whole point of using e.g. the var command to declare the variable was for python to create an entire class around that name, together with associated mathematical associations and operations. We get to these using the usual method notation: the variable name, followed by a period and the name of the method we want to apply. For example, the expand function expands polynomial expressions. We can reverse this by using factor. If we need to substitute one expression for another, there is a subs method to do that.

>>> newexpr=expr.expand() >>> newexpr u**4 + 4*u**3*v + 6*u**2*v**2 + 4*u*v**3 + v**4 >>> newexpr.factor() (u + v)**4 >>> newexpr.subs(u,2) v**4 + 8*v**3 + 24*v**2 + 32*v + 16 >>> (newexpr.subs(u,2)).factor() (v + 2)**4

Notice in the last expression that we simply put parentheses around a computed quantity, which was symbolic because it was created from symbolic expressions, and then used a method associated with this new symbolic object. Once again for emphasis: any time we perform a computation that uses a symbolic variable, the result is itself symbolic, and hence has access to all the methods associated with symbolic variables.

When we use symbolic expressions, we really don't want to have to look at all that "**" stuff. We want to see something that resembles ordinary mathematical notation. Many symbolic packages call this "pretty printing". Regrettably, the result is not always pretty, but most of the time it produce result that are more legible than the straight python syntax. To do pretty printing using sympy, run three commands in your session:

```>>> import sys
>>> oh=sys.displayhook
>>> sys.displayhook=pprint
```
After that is done, your expressions should be "pretty printed".

>>> u=1/((y+3)*(y-4)**2) >>> u 1/((y - 4)**2*(y + 3)) >>> import sys >>> oh=sys.displayhook >>> sys.displayhook=pprint >>> u 1 ---------------- 2 (y - 4) *(y + 3)

In all further discussion of the sympy package, we will suppose that you have run these commands.

Sympy can manipulate fractional expressions. For example, it contains a command that every symbolic manipulation program in history has had: simplify.

>>> simplify(newexpr/(u+v)) 3 2 2 3 u + 3*u *v + 3*u*v + v >>> (newexpr/(u+v)).simplify() 3 2 2 3 u + 3*u *v + 3*u*v + v >>> trigexp=sin(u)**2+cos(u)**2 >>> trigexp 2 2 sin (u) + cos (u) >>> trigexp.simplify() 1

Simplify looks for common factors in the numerator and denominator of an expression and cancels them if they are found. It also tries to apply some simple rules about exponents and trigonometric functions.

Another requirement in dealing with fractional expressions is combining and breaking up expressions with different denominators. For this sympy gives us together and apart. The point is that apart is doing partial fraction decompositions for you.

>>> expr = 1/((u+1)*(u-3)) >>> newexpr=apart(expr) >>> newexpr 1 1 - --------- + --------- 4*(u + 1) 4*(u - 3) >>> together(newexpr) 1 --------------- (u - 3)*(u + 1)