from scipy.integrate import quad
8 Scientific Python (SciPy)
Scipy is a powerful open-source scientific computing library for the Python programming language. It was created in 2001 by Travis Oliphant, a computational scientist, and over the years, it has grown to become one of the most widely used libraries in the scientific and engineering communities.
Scipy provides a wide range of modules for tasks such as numerical integration, optimization, signal processing, linear algebra, and more. It is built on top of the NumPy library, which provides support for arrays and matrices. The combination of Scipy and NumPy creates a powerful toolset for scientific computing in Python.
The library is continuously maintained and updated by a team of developers, and it is widely used in academia, research, and industry. Its primary goal is to provide efficient and easy-to-use tools for scientific computing and data analysis.
Some of the key applications of Scipy include machine learning, image processing, signal processing, optimization, and simulation. It is also commonly used in fields such as physics, chemistry, biology, and engineering.
Overall, Scipy has become an essential tool for anyone working in scientific computing and data analysis in Python, and its continued development and innovation make it an exciting library to work with.
8.1 Integration
Numerical evaluation of a function of the type
\(\displaystyle \int_a^b \int_a^b f(x, y) dxdy\)
can be performed using numerical quadrature. In SciPy, for e.g. the quad
function and dblquad
among others, can be used for 1 variable and double integration respectively. Multiple variable integration is also available.
# define a simple function for the integrand
def f_1d(x, a, b):
"""
implements a simple quadratic function, with intercept at zero
"""
return (a**b)*x**2
= 0 # the lower limit of x
x_lower = 1 # the upper limit of x
x_upper
= quad(f_1d, x_lower, x_upper, args=(1, 2))
val, abserr print(val, abserr)
0.3333333333333333 3.700743415417188e-15
8.2 Ordinary differential equations (ODEs)
SciPy is often used to solve ODEs and system of coupled ODEs ! The odeint
function is used to solve ODEs.
8.2.1 Lotka-Volterra Equations
Let’s solve a simple 2D system of ordinary differential equations (ODEs) and plot the results. We’ll use the solve_ivp
function from SciPy’s integrate
module.
Consider the following system of ODEs:
\[ \frac{dx}{dt} = f(x, y) \] \[ \frac{dy}{dt} = g(x, y) \]
where \(x\) and \(y\) are the dependent variables, and \(f(x, y)\) and \(g(x, y)\) are functions that describe the rates of change of \(x\) and \(y\) with respect to time \(t\).
For this example, let’s use the Lotka-Volterra equations (predator-prey model):
\[ \frac{dx}{dt} = \alpha x - \beta xy \] \[ \frac{dy}{dt} = \delta xy - \gamma y \]
where \(\alpha\), \(\beta\), \(\gamma\), and \(\delta\) are constants.
Here’s the Python code to solve this system and plot the results:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
# Define the Lotka-Volterra equations
def lotka_volterra(t, z, alpha, beta, delta, gamma):
= z
x, y = alpha * x - beta * x * y
dxdt = delta * x * y - gamma * y
dydt return [dxdt, dydt]
# Parameters
= 1.0
alpha = 0.1
beta = 0.075
delta = 1.5
gamma
# Initial conditions: [prey population, predator population]
= [10, 5]
z0
# Time span
= (0, 10)
t_span = np.linspace(*t_span, 1000)
t_eval
# Solve the system of ODEs
= solve_ivp(lotka_volterra, t_span, z0, args=(alpha, beta, delta, gamma), t_eval=t_eval)
solution
# Extract the results
= solution.t
t = solution.y[0]
x = solution.y[1]
y
# Plot the results
= plt.subplots(figsize=(6, 6))
fig, ax
# Plot prey population
='Prey (x)', color='blue')
ax.plot(t, x, label
# Plot predator population
='Predator (y)', color='red')
ax.plot(t, y, label
# Add labels and legend
'Time')
ax.set_xlabel('Population')
ax.set_ylabel(
ax.legend()
ax.grid()
plt.show()
Running this code will produce a plot showing the dynamics of the prey and predator populations over time according to the Lotka-Volterra model.
8.2.2 Modelling Infection and recovery
At rate \(\beta\), when a susceptible meets an infected, two infected persons exist. \(S + I \rightarrow 2 I\) At rate \(\gamma\) the infected recovers ! \(I \rightarrow R\)
R1 | R2 | |
---|---|---|
S | -1 | 0 |
I | +1 | -1 |
R | 0 | 1 |
Let us consider a simple epidemiological model that simulates the infection and recovery of a population ! The ordinary differential equations describing this is given by:
\[\begin{aligned} & \frac{dS}{dt} = - \beta I S, \\ & \frac{dI}{dt} = \beta I S- \gamma I, \\ & \frac{dR}{dt} = \gamma I, \end{aligned}\].
from scipy.integrate import odeint
= 0.01
beta = 0.5
gamma
def dx(x, t):
"""
The right-hand side of the model
"""
= x[0]
S = x[1]
I = x[2]
R
= -beta*I*S*t
dS = beta*I*S -gamma*I
dI = gamma*I
dR
return [dS, dI, dR]
# choose an initial state
= [90, 10, 0] x0
# time coodinate to solve the ODE for: from 0 to 10 days
= np.linspace(0, 20, 350) t
# solve the ODE problem
= odeint(dx, x0, t) x
8.3 Optimization
Let us look at a few very simple cases of optimization. For a more detailed introduction to optimization with SciPy see: http://scipy-lectures.github.com/advanced/mathematical_optimization/index.html
from scipy import optimize
8.3.1 Finding a minima of a 1D function
Let’s first look at how to find the minima of a simple function of a single variable:
def f(x):
"""
a function that returns the square of the input data
"""
return x**2
We can use the fmin_bfgs
function to find the minima of a function:
= optimize.fmin_bfgs(f, -2)
x_min x_min
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 2
Function evaluations: 6
Gradient evaluations: 3
array([-1.79994222e-08])
-5) optimize.fmin_bfgs(f,
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 3
Function evaluations: 8
Gradient evaluations: 4
array([-2.54405594e-08])
We can also use the brent
or fminbound
functions. They have a bit different syntax and use different algorithms.
8.3.2 Optimization of a 2D function
Let’s consider a common 2D objective function, the Rosenbrock function, which is often used to test optimization algorithms. The function is defined as:
\[f(x, y) = (a - x)^2 + b(y - x^2)^2 \]
where \(a\) and \(b\) are constants. For our example, let’s set \(a = 1\) and \(b = 100\).
We’ll use SciPy’s optimization capabilities to find the minimum of this function and create a contour plot with contour labels.
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
# Define the Rosenbrock function
def rosenbrock(x):
= 1
a = 100
b return (a - x[0])**2 + b * (x[1] - x[0]**2)**2
# Perform the optimization
= [0, 0]
initial_guess = minimize(rosenbrock, initial_guess, method='BFGS')
result = result.x
opt_x, opt_y
# Generate a grid of points
= np.linspace(-2, 2, 400)
x = np.linspace(-1, 3, 400)
y = np.meshgrid(x, y)
X, Y = (1 - X)**2 + 100 * (Y - X**2)**2
Z
# Plot the contour
= plt.subplots(figsize=(7, 7))
fig, ax = ax.contour(X, Y, Z, levels=np.logspace(-1, 3, 20), cmap='viridis')
contour =True, fontsize=8)
ax.clabel(contour, inline'r*', markersize=10, label='Optimum')
ax.plot(opt_x, opt_y, 'x')
ax.set_xlabel('y')
ax.set_ylabel(
ax.legend()#plt.colorbar()
plt.show()
8.4 Further reading
Here are some of the most frequently used functions from the SciPy library:
scipy.integrate.quad: Computes a definite integral of a function from a to b using Gaussian quadrature.
scipy.optimize.minimize: Finds the minimum of a function using various optimization algorithms.
scipy.stats.norm: Provides functions for working with normal (Gaussian) distributions, including PDF, CDF, and random number generation.
scipy.linalg.eig: Computes the eigenvalues and eigenvectors of a square matrix.
scipy.signal.convolve: Computes the convolution of two signals.
scipy.fft.fft: Computes the one-dimensional discrete Fourier Transform.
scipy.special.gamma: Computes the gamma function and related functions.
scipy.interpolate.interp1d: Interpolates a one-dimensional function.
scipy.spatial.distance.pdist: Computes pairwise distances between observations in a dataset.
scipy.cluster.hierarchy.linkage: Computes hierarchical clusters from a distance matrix.