from numpy import sin, pi
/4) sin(pi
0.7071067811865475
Scientific computing software plays a major role in biomathematics for many reasons some of which include:
The complexity of biological systems leads to models or equations that are difficult to work with analytically but can be handled reasonably well by numerical methods.
The need to synthesize theoretical models and data from experiments.
Plots and other visualizations are important for understanding and communicating scientific ideas and results.
Computing allows us to use theoretical models to conduct experiments in silico that would be difficult or impossible to conduct otherwise.
Thus, this course will make use of scientific computing software. Three commonly used free and open-source languages for scientific computing in biomathematics are R, Julia, and Python. In this course, we will mostly use R, but it’s worth knowing at least a little about scientific computing in all three languages as each of them has its own strengths and weaknesses in the context of scientific computing for biomathematics. The goal is not to teach you to become an expert in any of these languages, but rather to become familiar with how to use them to solve some basic problems in biomathematics. Thus, this document is more of a reference than a tutorial.
This page will focus on Python. The other two languages have their own pages:
Each of these pages will have a similar structure to this one. We do not provide details on or even an introduction to the basics of programming or the structure of any of the languages. There are many resources available for learning these things. Instead, we focus on providing some concise examples of the use of these languages for scientific computing in biomathematics. The hope is that the reader will be able to copy and modify these examples to solve their own problems.
The section titles listed in the table of contents for the page should indicate the topics or types of problems we provide examples for. For some of the more involved code, we provide links to additional webpages that provide more details. In some cases, we provide links to potentially helpful videos or web sites where the reader can learn more.
Python is an elegant an mature general-purpose programming language (Van Rossum and Drake 2009). You can learn more about the history of Python here.
As in R, most of the algorithms and methods we will want to use in solving problems in biomathematics are implemented as functions in Python. However, base Python contains very few functions useful for mathematics or scientific computing. Most of the functions we will need are contained in modules. A module is a collection of functions and other objects that can be imported into Python. There are many modules available for scientific computing in Python. We will focus on a few of the most commonly used ones such as
NumPy: Numerical Python, offers comprehensive mathematical functions, random number generators, linear algebra routines, Fourier transforms, and more.
SciPy: Scientific Python, provides algorithms for optimization, integration, interpolation, eigenvalue problems, algebraic equations, differential equations, statistics and many other classes of problems.
Matplotlib: Plotting in Python, is a comprehensive library for creating static, animated, and interactive visualizations in Python.
SymPy: Symbolic Python, is a library for symbolic mathematics.
py-pde: py-pde is a python package providing methods and classes useful for solving partial differential equations (PDEs).
One thing that is a little different working in Python (and also Julia) compared to R is that it’s common practice to utilize virtual environments to avoid conflicts between functions and objects from different modules. Environments also help keep track of different versions of the Python interpreter. We will not go into details about how to use environments here, but we will provide some links to resources that explain how to use them.
The most common tool in the scientific community for managing Python environments is Conda. Conda is a package manager that can be used to install Python and other software packages. It can also be used to create and manage virtual environments. Conda is available for Windows, Mac, and Linux. You can download and install Conda from here. To learn about using Conda, see the following video:
The nice thing about working with virtual environments is that we can easily share the environment with others. To do this, we can create a file called environment.yml
that contains a list of all the packages we want to install in the environment. For example, the environment.yml
file from the GitHub repository will create an environment called biomath_py
that contains Python 3.9 and the NumPy, SciPy, Matplotlib, and SymPy modules among others.
Once we have set up and activated the environment, we can import modules and use functions from them. For example, to import the NumPy module and compute
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['text.usetex'] = True
x = np.linspace(-2.0, 2.0, 101)
y2 = np.power(x,2)
def f(x):
return np.power(x,2)
fig, ax = plt.subplots(figsize=(7, 5), tight_layout=True)
ax.set_xlabel(r'\textit{$x$}',fontsize=13)
ax.set_ylabel('\\textit{$y$}',fontsize=13)
ax.set_title(r'$f(x) = x^2$',fontsize=15)
ax.plot(x, f(x))
def g(x):
return np.power(x,3)
# Set custom figure size
plt.figure(figsize=(8, 6))
# Plot the curves
plt.plot(x, f(x), label=r'$x^2$', color='steelblue', linewidth=2)
plt.plot(x, g(x), label=r'$x^3$', color='purple', linewidth=2)
# Add labels and title
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(r'$y$',fontsize=13)
plt.title(r'$f(x) = x^2$',fontsize=13)
# Add legend
plt.legend()
# Show the plot
plt.grid(True)
plt.show()
# Create subplots
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(8, 5))
# Plot y = x^2
ax1.plot(x, f(x), color='steelblue', linewidth=2)
ax1.set_title(r'$f(x) = x^2$')
ax1.set_xlabel(r'$x$')
ax1.set_ylabel(r'$y$')
ax1.grid(True)
# Plot y = x^3
ax2.plot(x, g(x), color='purple', linewidth=2)
ax2.set_title(r'$f(x) = x^3$')
ax2.set_xlabel(r'$x$')
ax2.set_ylabel(r'$y$')
ax2.grid(True)
# Adjust layout to prevent overlap
plt.tight_layout()
# Show the subplots
plt.show()
Here’s a vector:
Here’s a matrix:
[[ 1. 0. -1.]
[ 3. 1. 2.]
[-1. 1. 2.]]
Here’s the matrix-vector product:
Here’s the determinant of the matrix:
Here are the eigenvalues and eigenvectors of the matrix:
[-0.73205081+0.j 2. +0.j 2.73205081+0.j]
[[ 0.23617375 -0.57735027 -0.49557222]
[-0.88141242 -0.57735027 0.13278818]
[ 0.40906493 0.57735027 0.85835626]]
Here’s the solution to the linear system:
To learn more, check out
Root finding is the process of finding one or more values
The scipy.optimize
module contains functions for root finding in Python. For example, following code uses the scipy.optimize.root_scalar
function to find the roots of the function
We start by plotting the function to get an idea of where the roots are located.
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return x**2 - 1
x = np.linspace(-2, 2, 101)
plt.figure(figsize=(8, 6))
plt.plot(x, f(x), color='steelblue', linewidth=2)
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(r'$y$',fontsize=13)
plt.title(r'$f(x) = x^2 - 1$',fontsize=13)
plt.grid(True)
plt.show()
Now, let’s compute the roots:
from scipy import optimize
def f(x):
return x**2 - 1
root_1 = optimize.root_scalar(f, bracket=[-2, 0])
root_2 = optimize.root_scalar(f, bracket=[0, 2])
print(root_1.root)
print(root_2.root)
-1.0
1.0
Finally, let’s add the roots to the plot:
plt.figure(figsize=(8, 6))
plt.plot(x, f(x), color='steelblue', linewidth=2)
plt.plot([root_1.root, root_2.root], [0, 0], 'o', color='purple', markersize=10)
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(r'$y$',fontsize=13)
plt.title(r'$f(x) = x^2 - 1$',fontsize=13)
plt.grid(True)
plt.show()
Here’s an example of using the scipy.optimize.root
function to find a of root of the system of equations
Optimization is the process of finding the extrema, that is, maximum or minimum value of a function. A common application of optimization in biomathematics is to estimate parameters for a mathematical model to fit data. One of the major challenges in such optimization problems is the potential for multiple local extrema. Here, introduce the use of basic optimization methods implemented in Python. Additional details, examples, and applications will be covered in course lectures.
The scipy.optimize
module contains functions for optimization in Python. For example, following code uses the scipy.optimize.minimize
function to find the minimum of the function
We start by plotting the function to get an idea of where the minimum is located.
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return x**2 - 1
x = np.linspace(-2, 2, 101)
plt.figure(figsize=(8, 6))
plt.plot(x, f(x), color='steelblue', linewidth=2)
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(r'$y$',fontsize=13)
plt.title(r'$f(x) = x^2 - 1$',fontsize=13)
plt.grid(True)
plt.show()
Now, let’s compute the minimum:
from scipy import optimize
def f(x):
return x**2 - 1
min_result = optimize.minimize(f, 0.2)
print(min_result.x)
[4.28408381e-09]
Finally, let’s add the minimum to the plot:
plt.figure(figsize=(8, 6))
plt.plot(x, f(x), color='steelblue', linewidth=2)
plt.plot(min_result.x, f(min_result.x), 'o', color='purple', markersize=10)
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(r'$y$',fontsize=13)
plt.title(r'$f(x) = x^2 - 1$',fontsize=13)
plt.grid(True)
plt.show()
Here’s an example of using the scipy.optimize.minimize
to find the minimum of the function
In this section, we provide an overview and links to detailed examples of the numerical solution of initial value problems (IVPs) for ordinary differential equations (ODEs). Additionally, the linked examples illustrate computations and visualizations of the phase plane and phase portraits for systems of ODEs. For the numerical solution of ODEs, we will use the solve_ivp
from the scipy.integrate
module.
Briefly, most implementations of numerical methods for IVPs for ODEs require the user to define a function that returns the value of the derivative of the solution at a given time, that is, the right hand side of the ODEs. Further, the user must specify an initial condition, a time interval over which to solve the ODEs, and the values of any parameters.
Partial differential equations (PDEs) generalize ordinary differential equations (ODEs) to allow for solutions that are functions of two or more variables. Typically, in the context of biomathematics, the variables represent time and spatial coordinates. Thus, PDEs allow us to model how a biological systems change in both time and space. The examples we will see in this course will mostly be convection-diffusion type equations or systems of such equations. These PDEs have the form
Special cases include diffusion equations of which the heat equation
is a special case. In one dimension, the heat equation is
Often, in order to have a complete model, we need to specify both initial conditions and boundary conditions for the PDE problem. For example in the case of the one-dimensional heat equation, we might specify the initial temperature distribution
There are many approaches to the numerical solution of PDEs. We will focus on the method of lines which discretizes the spatial domain and then uses ODE solvers such as those we previously described to solve the resulting system of ODEs.
For Python, the py-pde
module is available to facilitate the implementation of the method of lines for PDEs. The following links illustrate the use of this package:
py-pde
module to obtain and visualize numerical solutions for the heat equation in one dimension.While MATH 463 primarily focuses on deterministic models, stochastic models are also important in biomathematics, see for example (Allen 2011). We won’t cover stochastic models in the course but will have a few occasions where it will be useful to add some stochasticity to a deterministic model. Many different probability distributions are available in Python through the scipy.stats
module. In this section, we introduce some functions associated with distributions and random sampling. Primarily, we focus on sampling from a distribution and accessing the probability density or probability mass function for a distribution. Further, we will demonstrate the main functions for the normal distribution and the binomial distribution. Working with other distributions is handled similarly. Additional details, examples, and applications will be covered in course lectures.
Recall that a binomial distribution is a discrete probability distribution that models the number of successes in a sequence of
where
Let’s work with a binomial distribution in Python. First, we import the binom
function from the scipy.stats
module:
Now, let’s define a binomial random variable with
We can now access the mean, variance, and standard deviation of the random variable:
Notice that the mean is
We can work with the probability mass function as follows:
Figure 4 displays the probability mass function for a binomial random variable with
# Figure size
plt.figure(figsize=(8, 6))
# Outcome values
x = np.arange(0,11)
# Plot the bar plot
plt.bar(x, binom_rv.pmf(x), color='steelblue', align='center')
# Customize the plot
plt.title('Binomial Distribution PMF')
plt.xlabel('Number of Successes')
plt.ylabel('Probability')
plt.xticks(x)
plt.text(8,0.21,r'$n=10, p =0.5$', fontsize=13)
# Display the plot
plt.show()
We can also sample from the binomial distribution:
Let’s generate a large number of samples and plot a histogram of the results, see Figure 5.
# Figure size
plt.figure(figsize=(8, 6))
# Generate 1000 samples
samples = binom_rv.rvs(size=1000)
# Plot the histogram
plt.hist(samples, bins=10, histtype='bar', ec="black", color='steelblue', align='left', rwidth=1)
# Customize the plot
plt.title('Binomial Distribution Samples')
plt.xlabel('Number of Successes')
plt.ylabel('Count')
plt.xticks(x)
plt.text(7.5,200,r'$n=10, p =0.5$',fontsize=13)
# Display the plot
plt.show()
Recall that a normal distribution is a continuous probability distribution that is symmetric about the mean, showing that data near the mean are more frequent in occurrence than data far from the mean. In graph form, normal distribution will appear as a bell curve. Normal distributions are extremely important in statistics and are often used in the natural and social sciences to represent real-valued random variables whose distributions are not known. A random variable with a Gaussian distribution is said to be normally distributed and is called a normal deviate. The probability that a normal random variable with mean
The parameter
Let’s work with a normal distribution in Python. First, we import the norm
function from the scipy.stats
module:
Now, let’s define a normal random variable with
Figure 6 displays the probability density function for a normal random variable with
# Figure size
plt.figure(figsize=(8, 6))
# Outcome values
x = np.linspace(0,10,100)
# Plot the pdf
plt.plot(x, norm_rv.pdf(x), color='steelblue', lw=2)
# Customize the plot
plt.title('Normal Distribution PDF')
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(' ')
plt.text(1,0.25,r'$\mu=5, \sigma =1.5$', fontsize=13)
# Display the plot
plt.show()
We can sample from a normal distribution with
array([4.89903623, 6.94486314, 3.73359347, 4.51750146, 4.02969902,
4.139107 , 5.04691272, 3.89045516, 4.7942513 , 4.91356273])
Let’s generate a large number of samples and plot a histogram of the results, see Figure 7.
# Generate samples
r = norm_rv.rvs(size=1000)
# Create figure
fig, ax = plt.subplots(1, 1)
# Outcome values
x = np.linspace(0,10,100)
# Plot the pdf
ax.plot(x, norm_rv.pdf(x), color='steelblue', lw=2)
# Plot the histogram
ax.hist(r, density=True, bins='auto', histtype='stepfilled', alpha=0.2)
# Customize the plot
plt.title('Normal Distribution PDF')
plt.xlabel(r'$x$',fontsize=13)
plt.ylabel(' ')
plt.text(1,0.25,r'$\mu=5, \sigma =1.5$', fontsize=13)
# Display the plot
plt.show()
Python version: 3.8.18 | packaged by conda-forge | (default, Oct 10 2023, 15:46:56)
[Clang 16.0.6 ]
numpy==1.24.4
scipy==1.10.1
matplotlib==3.7.2
Note that we allow for the function