MathCAD established a solid position when it came to symbolic computations. It was my pleasure to share the very first moments of MathCAD Plus 6.0 (1995) software when I came across some heavy statistical problems that only a computer could help me in simplifying complex mathematical formulae into digestable form, easy to evaluate, plot, and seek for results. The major problem was (and still is) around hefty licence fees you need to pay in order to enjoy your work without limitations.
Python’s response to symbolic computations delivering pinpoint solutions (free of charges) is its sympy module. It grew to the position of a serious threat to Mathematica and has been winning the hearts and minds of many mathematicians, scientists, and researchers over the past few years.
Symbolic computations helps financial quants too. Continuous efforts in searching for stable solutions in model validation, exotic derivative pricing and backtesting, risk hedging and applications development is often based on results derived by machines.
You can find the sympy module’s full documentation at http://
docs.sympy.org website. It is too extensive to describe here its complete spectrum of possibilities here. The typical applications cover calculus, algebra, and dedicated solvers. I do strongly encourage you to explore it further if you’re seeking for analytical solutions related to your current work, projects, or problems.
From our current point of view, let’s see how one can easily combine Python’s lists with sympy’s selected goodies.
Derivation of n-th derivative of log(1+x) function: (a) based on n-th term's formula; (b) win-th a help of sympy module.
It is easy to find iteratively that for:
its first four derivatives are:
f (x) = ln(1 + x) Code 2.22
Given the n-th term, first, let’s generate the Python’s list that stores the values of derivatives for x = 3 at n = 10.
from math import factorial
def df(x, n):
return (-1)**(n-1) * factorial(n-1) / (1+x)**n x2 = 3 # x = 3
n = 10 res = []
for i in range(1, n+1):
res.append(df(x2, i)) print("%10.3f" % res[-1])
We define a custom function, df, that accepts two input parameters
x and n and returns the value of derivative for n-th term. In a loop we iterate over i to be between 1 and 10 and append a list of res with computed value of the i-th derivative and print its last item out by making a reference to the last list’s element by res[-1]. When run, the output is:
0.250 -0.062 0.031 -0.023 0.023 -0.029 0.044 -0.077 0.154 -0.346
Excellent. Now, let’s employ symbolical computations of n derivatives of log(1+x) with a help of the sympy module in the following way:
from sympy import symbols, diff x = symbols("x")
f = "log(1+x)" # string res2 = []
for i in range(1, n+1):
f = diff(f, x) # find i-th derivative of f print("%2g-th derivative is %20s" % (i, f)) v = f.subs(x, x2) # derivative value at x = 3
f0(x) = 1 1 + x f00(x) = 1
(1 + x)2 f000(x) = ( 1)( 2) 1
(1 + x)3 f0v(x) = ( 1)( 2)( 3) 1
(1 + x)4 = ( 1)3 3!
(1 + x)4 ...
f0n(x) = ( 1)n 1(n 1)!
(1 + x)n 9 n 1
sympy.symbols()
sympy.diff() .subs()
res2.append(v)
print("\t\tits value at x = 3 is %10.3f\n" % res2[-1]) print(res == res2)
We make use of two functions, namely, symbols and diff. The former informs Python that a new variable x is from now on
"symbolical". The latter allows for a symbolical differentiation of any expression containing such defined x. Therefore, our fundamental function, log(1+x), is given as a string variable in f.
What follows is the equivalent of the loop we created a moment ago. In the loop, first, we compute a symbolical expression of the i-th derivative of function and with the assistance of the .subs
function we find its value at point x = 3. The results we save in a new list, res2, and in the end with check have we derived the identical derivatives’ values in both approaches:
1-th derivative is 1/(x + 1) its value at x = 3 is 0.250 2-th derivative is -1/(x + 1)**2
its value at x = 3 is -0.062 3-th derivative is 2/(x + 1)**3
its value at x = 3 is 0.031 4-th derivative is -6/(x + 1)**4
its value at x = 3 is -0.023 5-th derivative is 24/(x + 1)**5
its value at x = 3 is 0.023 6-th derivative is -120/(x + 1)**6
its value at x = 3 is -0.029 7-th derivative is 720/(x + 1)**7
its value at x = 3 is 0.044 8-th derivative is -5040/(x + 1)**8
its value at x = 3 is -0.077 9-th derivative is 40320/(x + 1)**9
its value at x = 3 is 0.154 10-th derivative is -362880/(x + 1)**10 its value at x = 3 is -0.346 True
Too easy. Please note that sympy delivers the results in a form of a string variable when f is inspected. The final test is a simple list comparison, element-wise. As we will learn in the next Chapter, the latter can be obtained if we apply:
import numpy as np
print(np.all(res == res2)) True
instead of print(res == res2) what enforces a boolean check, element-wise, vouchsafing that all elements of both lists are matching and are the same (in terms of floating-point precision).
Using sympy verify that for n > 2:
and find for what n one reaches 99.0% of the limit accuracy making use of Python’s list. Add "*" to the list after the corresponding value.
In this example we will try to supplement the .append with the .insert function for the lists. The latter allows us to specify a position (index) at which a new object will be added to the list. By adding "*" to the list we may wish to make a mark inside the list itself. Such construction may be used for data separation into segments within further data processing or analysis.
Let’s have a look at the complete code first:
from sympy import Symbol, limit from sympy import init_printing from math import trunc
init_printing() # a lovely sympy’s printing included n = Symbol("n")
expr = n**(-1/(n-1)) # define an expression
print("lim_{n --> 9} %s = %.5f" % (expr, limit(expr, n, 9))) print("lim_{n --> +oo} %s = %.5f\n" %
(expr, limit(expr, n, "+oo"))) print("%8s %12s %17s" % ("n", "exact", "approx"))
values = []
N = 1000
for i in range(3, N+1):
x = float(expr.subs(n, i)) values.append(x)
if(i < 10) or (i == N):
print("%8d %10.10f %17s" % (i, x, expr.subs(n, i))) elif(i == N-1):
print(" ...")
# find 1st item corresponding to 99% of the limit accuracy tmp = [trunc(x*100)/100 for x in values]
i = tmp.index(0.99) # find index; 642 print()
print(values[i-1:i+3])
# add "*" token to the list values.insert(i+1, "*") print(values[i-1:i+4])
We employ the limit function from the sympy module for deriving limits. As you will find, the syntax is intuitive. In the next step, inside the loop, we derive the exact value for the limit at each n->i
and append it to the global list with results, values. Solely for printing purposes, we allow a comparison of the exact value with the best approximation of it found by sympy. In order to inspect its Code 2.23
nlim!1
1
np1
n = 1
sympy.limit sympy.init_printing()
.insert(index, object)
form, the function of init_printing() has be initialised in the beginning.
99.0% of the limit we find by making use of list comprehension.
An expression trunc(x*100)/100 creates a temporary list with exact values in values list, limited to three decimal places only. Though slow for large N, this step allows us quickly to localise the very first element equal to 0.99. We achieve that with the help of the .index
function (as discussed earlier in this Chapter).
Adding "*" to the list at any requested position (index) is done by .insert. Painlessly. The output of Code 2.23 delivers:
lim_{n --> 9} n**(-1/(n - 1)) = 0.75984 lim_{n --> +oo} n**(-1/(n - 1)) = 1.00000 n exact approx 3 0.5773502692 sqrt(3)/3 4 0.6299605249 2**(1/3)/2 5 0.6687403050 5**(3/4)/5 6 0.6988271188 6**(4/5)/6 7 0.7230200264 7**(5/6)/7 8 0.7429971446 2**(4/7)/2 9 0.7598356857 3**(3/4)/3 ...
1000 0.9931091814 10**(332/333)/10 [0.98999, 0.99000, 0.99001, 0.99003]
[0.98999, 0.99000, '*', 0.99001, 0.99003]
where a funny "+oo" sign stands for in sympy. 99% accuracy of the limit at is achieved for 642th term.