Using some numerical examples, we will review some Python concepts. We will also introduce some specific numerical computation modules.
numpy
import numpy as np
import matplotlib.pyplot as plt
import time
f = lambda x: np.exp(x)
x = np.linspace(-10,10,300000)
Let us create see three different ways of creating a vector that conteins the values of the function f
for the values of x
for i in range(4):
print(i)
We start with a numpy array of size zero. We will add elements with np.append
. We measure the execution time t1
as the difference in the machine time before and after the loop
y = np.array([])
t = time.time()
for i in range(len(x)):
y = np.append(y,f(x[i]))
t1 = time.time() - t
Now we reserve memory creating a numpy array of zeros of the same shape and size than x
and we fill the elements one by one with the loop
y = np.zeros_like(x)
t = time.time()
for i in range(len(x)):
y[i] = f(x[i])
t2 = time.time() - t
And, finally, we use the numpy vectorization: its functions (and lambda is a numpy function) can work for numpy arrays.
t = time.time()
y = f(x)
t3 = time.time() - t
We compare the execution time
print('Vector growing inside the loop: ', t1,' seconds')
print('Without vectorization: ', t2,' seconds')
print('With vectorization: ', t3,' seconds')
print('t1/t2 = ', int(t1/t2))
print('t2/t3 = ', int(t2/t3))
print('t1/t3 = ', int(t1/t3))
We can see that we have executed the options from worse to better. Thus, we will avoid growing vectors inside a loop and use vectorization.
plot
function, from module matplotlib.pyplot
, really plots a polygonal line that joins function points. For instance:
f = lambda x: np.exp(x)
We choose the interval $[a,b]$ where we will plot the function
a = -1.; b = 1.
We create a five equally-spaced point mesh in the $[a,b]$ interval, that is stored in a numpy array x
.
x = np.linspace(a,b,5)
print(x)
The corresponding numpy array for y
is computed with
y = f(x)
print(y)
plt.figure()
plt.plot(x,y)
plt.show()
The approach is quite rough with only 5 points. By default, linspace
creates a 50 point mesh. We repeat the process with 50 points.
x = np.linspace(a,b)
y = f(x)
plt.figure()
plt.plot(x,y)
plt.show()
It is also a polygonal line, but now there are enough points to look like a smooth curve.
Let us add some details to the graph
OX = 0*x # 50 zeros numpy array. To plot OX axis
plt.figure()
plt.plot(x,y, label = 'f')
plt.plot(x,OX,'k', label = 'OX axis') # k, from "black"
plt.title('Example function f plot')
plt.legend() # using the "label", it plots a legend
plt.show()
McLaurin series (Taylor series with $x_0=0$) for function $f(x)=e^x$ is
$$ e^x = 1 + \dfrac{x^1}{1!}+\dfrac{x^2}{2!}+\dfrac{x^3}{3!}+\dfrac{x^4}{4!}+\dfrac{x^5}{5!}+\cdots $$Theoretically, if we add enough terms we can approximate the function as much as we want. The successive polynomials are
$$ \begin{array}{rcl} P_{0}(x) & = & 1\\ P_{1}(x) & = & 1 + \dfrac{x^1}{1!}\\ P_{2}(x) & = & 1 + \dfrac{x^1}{1!}+\dfrac{x^2}{2!}\\ P_{3}(x) & = & 1 + \dfrac{x^1}{1!}+\dfrac{x^2}{2!}+\dfrac{x^3}{3!}\\ P_{4}(x) & = & 1 + \dfrac{x^1}{1!}+\dfrac{x^2}{2!}+\dfrac{x^3}{3!}+\dfrac{x^4}{4!}\\ & \ldots & \\ \end{array} $$and $P_n$ approaches the function better than polynomial $P_{n-1}$. Let us compute the value of degree 3 polynomial in $x_0=0.5$. Taking into account that $x_0^0 = 1$ and $0!=1$
x0 = 0.5
polynomial = 0.
factorial = 1.
for i in range(4):
term = x0**i/factorial
polynomial += term
factorial *= i+1
print('P3(0.5) = ', polynomial)
print('np.exp(0.5) = ', np.exp(x0))
def P(x0,degree):
polynomial = 0.
factorial = 1.
for i in range(degree + 1):
term = x0**i/factorial
polynomial += term
factorial *= i+1
return polynomial
print('P(0.5, 10) = ', P(0.5, 10))
print('np.exp(0.5) = ', np.exp(0.5))
If the input argument is a numpy array, the output is also a numpy array
a = -1.; b = 1.
x = np.linspace(a,b,5)
print('x = ', x)
print('P(x, 10) = ', P(x, 10))
print('np.exp(x) = ', np.exp(x))
If we use a 50 points numpy array, we can draw the polynomial. Let us graph the 2 degree polynomial using a red line
a = -1.; b = 1.
f = lambda x: np.exp(x)
x = np.linspace(a,b)
OX = 0*x
plt.figure()
plt.plot(x,f(x), label = 'f')
plt.plot(x,OX,'k')
plt.plot(x,P(x,2),'r', label = 'P2')
plt.title('Example of function and polynomial graph')
plt.legend()
plt.show()
No we change the interval to $[-3,3]$ and, using a loop, we plot more polynomials
a = -3.; b = 3.
f = lambda x: np.exp(x)
x = np.linspace(a,b)
OX = 0*x
plt.figure()
plt.plot(x,f(x), label = 'f')
plt.plot(x,OX,'k')
for degree in range(1,5):
plt.plot(x,P(x,degree), label = 'P'+str(degree))
plt.title('Function and approximation polynomials')
plt.legend()
plt.show()
If we add plt.pause(1)
we can see a small animation on screen using spyder. For this purpose:
import numpy as np
import matplotlib.pyplot as plt
#%%--------------------------------------
def P(x0,degree):
polynomial = 0.
factorial = 1.
for i in range(degree + 1):
term = x0**i/factorial
polynomial += term
factorial *= i+1
return polynomial
#%%--------------------------------------
a = -3.; b = 3.
f = lambda x: np.exp(x)
x = np.linspace(a,b)
y = f(x)
OX = 0*x
plt.figure()
plt.plot(x,y, label = 'f')
plt.plot(x,OX,'k')
for degree in range(1,7):
plt.plot(x,P(x,degree), label = 'P'+str(degree))
plt.title('Function and approximation polynomials')
plt.legend()
plt.pause(1)
plt.show()
i = 0
while i < 5:
print(i)
i += 1
Using a while loop, write a script, that using the McLaurin polynomials for the function $f(x)=e^x$, computes its approximate value at the point x0 = -0.4
. The stopping criteria are that the last added term in absolute value is less than a tolerance value tol=1.e-8
and the maximum number of terms is 100, that is maxNumSum=100
. Compare the result with the function lambda f value at the same point. Print the number of iterations.
Hints:
a
can be obtained with np.abs(a)
%run Exercise1.py
Modify the script of Exercise 1 to compute simultaneously the function approximate value in the 50 points contained in x = np.linspace(-1,1)
. The stopping criteria are that the last term in absolute value (now we have 50 different $x_0$ and 50 different last terms), is less than a tolerance tol=1.e-8
and that the maximum number of terms is 100, maxNumSum=100
.
Transform the script into a function funExp(x, tol, maxNumSum)
whose input arguments are the numpy array x
of 50 $x$ values, tol
and maxNumSum
and whose output argument is a numpy array y
of the approximate values of the function in the $x$ points obtained using McLaurin polynomial. Use this function to plot the function. Plot also the corresponding lambda
function defined from np.exp(x)
.
Hints:
np.max(np.abs(term))
gives the maximum value of all the absolute values of the values contained in the numpy array term
.plt.plot(x,y,'y',linewidth=5)
plt.plot(x,y,'b--')
%run Exercise2.py
Using a while loop, write a script, that using the McLaurin polynomials for the function $f(x)=\sin(x)$, computes its approximate value at the point x0 = np.pi/4
. The stopping criteria are that the last added term in absolute value is less than a tolerance value tol=1.e-8
and the maximum number of terms is 100, that is maxNumSum=100
. Compare the result with the function lambda f value at the same point. Print the number of iterations.
Hints:
%run Exercise3.py
Modify the script of the previous exercise to compute simultaneously the function approximate value in the 50 points contained in x = np.linspace(-np.pi,np.pi)
. The stopping criteria are that the last term in absolute value (now we have 50 different $x_0$ and 50 different last terms), is less than a tolerance tol=1.e-8
and that the maximum number of terms is 100, maxNumSum=100
.
Transform the script into a function funSin(x, tol, maxNumSum)
whose input arguments are the numpy array x
of 50 $x$ values, tol
and maxNumSum
and whose output argument is a numpy array y
of the approximate values of the function in the $x$ points obtained using McLaurin polynomial. Use this function to plot the function. Plot also the corresponding lambda
function defined from np.sin(x)
.
%run Exercise4.py
McLaurin series for function $\mathrm{sinh}$ is
$$\mathrm{sinh}\;x = \dfrac{x^1}{1!}+\dfrac{x^3}{3!}+\dfrac{x^5}{5!}+\dfrac{x^7}{7!}+\dfrac{x^9}{9!}+\cdots$$and for $\mathrm{cosh}$
$$\mathrm{cosh}\;x = 1+\dfrac{x^2}{2!}+\dfrac{x^4}{4!}+\dfrac{x^6}{6!}+\dfrac{x^8}{8!}+\cdots$$and as $\mathrm{tanh}$
$$ \mathrm{tanh}=\dfrac{\mathrm{sinh}\;x}{\mathrm{cosh}\;x} $$compute the $\mathrm{tanh}$ for $x_0=0.5$ from McLaurin series of sinh and cosh, using the same number of terms in the numerator and denominator. Stop when two consecutive approximations of $\mathrm{tanh}(x_0)$ differ by less than $10^{-4}$.
%run Exercise5.py
Modify the script of the previous exercise to compute simultaneously the function approximate value in the 50 points contained in x = np.linspace(-3,3)
. The stopping criterion is that the difference between two consecutive approximations is less than a tolerance tol=1.e-8
for all the 50 points in x
.
Transform the script into a function funTanh(x, tol)
whose input arguments are the numpy array x
of 50 $x$ values, tol
and whose output argument is a numpy array y
of the approximate values of the function in the $x$ points obtained using McLaurin polynomial. Use this function to plot the function. Plot also the corresponding lambda
function defined from np.tanh(x)
.
%run Exercise6.py