4. Dynamical systems as Linear Transformations#
Credit: Ben Vanderlei’s Jupyter Guide to Linear Algebra under CC BY 4.0 with minor adaptations.
4.1. Discrete dynamical systems#
In week 1 we learnt the basics of algebra, now we are going to see how these concepts we learn such vector, matrix or eigenvalues can be useful to study dynamical systems. Giving us a complentary view to that of differential equations.
It is often useful to describe a structure that has multiple components with a single vector. If that structure is changing in time due to some process, it is typical to refer to the vector as a state vector since it describes the state of the structure at some particular time. To be less abstract, an example would be if you want to quantify the concentration of different neurotransmitters in a synaptic cleft: you could represent the concentration of each of them as a value in a vector. Equivalently, if you are studying the movement of an animal, you could represent the position of each of animal joint as value in the vector so you can track its movement during an experiment.
It is quite common to model such dynamic processes at discrete times and use linear transformations to model the evolution of the state vector from one time to the next.
Let’s suppose that we aim to describe sequence of vectors at times
4.1.1. Infectious Disease Model#
In this example we consider a basic model of an infectious disease that is spreading within a population. A well known family of models for this scenario is known as the
We suppose that the population is completely homogeneous in all regards, so that all individuals in a given category have the same probabilities to move to the next category.
To model real-world epidemics, it is necessary to estimate some parameters that specify how quickly individuals move among the categories. These parameters will be important in making any predictions with the model. For our demonstration, we will create an example. Let us suppose that our state vectors describe the population at time intervals of 1 week, and that every week, 5% of the Susceptible population becomes Infectious, and 20% of the Infectious population becomes Recovered. We also suppose that 15% of the Recovered population again becomes Susceptible every week.
If we let
Notice that since the numbers represent percentage, each column must add up to 1.
Now we can define
The linear transformation
We compute
import numpy as np
A = np.array([[0.95, 0, 0.15],[0.05,0.8,0],[0,0.2,0.85]])
## X at time 0
X_0 = np.array([[0.95],[0.05],[0]])
## Compute X at the next time
X_1 = A@X_0
print(X_1)
[[0.9025]
[0.0875]
[0.01 ]]
Applying the transformation again gives
## X at time 0
X = np.array([[0.95],[0.05],[0]])
for t in range(50):
X = A@X
print(X)
[[0.63157999]
[0.15789071]
[0.2105293 ]]
In such models attention is typically focused on the ultimate behavior of the state vector. We want to know if the composition of the population reaches an equilibrium, or continues to change. Let’s have a visual look at the model behavior:
4.2. Exercises#
Make a plot the evolution over time of the susceptible-infected-recovered population. Hint: you will have to create a list variable to store the values of each group over time and then apply the matrix
to the initial state vector like we did in the previous code cell.Label the axes of the plot and add a legend.
import matplotlib.pyplot as plt
import numpy as np
## Your code here
4.3. Infectious disease model - Continuation#
The notions of eigenvalues and eigenvectors that we have talked about in the previous notebook can be very useful to study the dynamical systems like the spread of a virus in the SIR model:
For the
Given an initial condition
In this case the components of the vector have individual meaning, so let’s calculate the first 30 iterations and plot
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
A = np.array([[0.95, 0, 0.15],[0.05,0.8,0],[0,0.2,0.85]])
## T is final time
T = 20
## X at time 0
X = np.array([[0.95],[0.05],[0]])
## The first column of results contains the initial values
results = np.copy(X)
for i in range(T):
X = A@X
results = np.hstack((results,X))
## t contains the time indices 0, 1, 2, ..., T
t = np.linspace(0,T,T+1)
## s, i, r values are the rows of the results array
s = results[0,:]
i = results[1,:]
r = results[2,:]
fig,ax = plt.subplots(figsize=(16, 12))
## The optional label keyword argument provides text that is used to create a legend
ax.plot(t,s,'b+',label="Susceptible");
ax.plot(t,i,'rx',label="Infective");
ax.plot(t,r,'g+',label="Removed");
ax.set_ylim(0,1.1)
ax.grid(True)
ax.legend();
Based on the calculation it appears that the state of the population has reached an equilibrium after 20 weeks. In the equilibrium state, each category of the population,
The equation
Before attempting to solve the system
Notice that in our case the matrix is singular, meaning that its determinant is zero and hence it doesn’t have an inverse:
from scipy import linalg
AminusI = A - np.identity(3)
print(f"Determinant of A: {linalg.det(AminusI)}")
Determinant of A: -1.1241008124329708e-18
Notice that -1e-18 is the scientific notation for the number 0.0000000000000000018
This is why the values for the inverse matrix are effectively infinity:
print('Inverse matrix of A:\n')
print(linalg.inv(AminusI))
Inverse matrix of A:
[[-2.25179981e+16 -2.25179981e+16 -2.25179981e+16]
[-5.62949953e+15 -5.62949953e+15 -5.62949953e+15]
[-7.50599938e+15 -7.50599938e+15 -7.50599938e+15]]
Again, notices that the number 1e15 is 10000000000000000
This means that the system doesn’t have an unique solution, therefore it can either have none or infinite. Since we know there is at least the trivial solution
4.4. Exercises#
Use Scipy linalg.eig() function to compute the eigenvalues and eigenvectors of the matrix
:
from scipy.linalg import eig
A = np.array([[0.95, 0, 0.15],[0.05,0.8,0],[0,0.2,0.85]])
eigenvalues, eigenvectors = ## Your code here
Cell In[7], line 5
eigenvalues, eigenvectors = ## Your code here
^
SyntaxError: invalid syntax
By default, eigenvectors are complex numbers, we can simply extract the real part by appending
.real
to an array.
eigenvalues = eigenvalues.real
eigenvectors = eigenvectors.real
print(eigenvalues)
print('\n')
print(eigenvectors)
Look for the eigenvector associated to the eigenvalue with the biggest real part and put in a variable as a numpy array:
eigenvector = ## Your code here
print(eigenvector)
Finally, we will need to impose the constraint on the found values that the sum of the the susceptible + infected + recovered individuals must be 1. We can do so by simply dividing the length of the vector:
normalised_eigenvector = eigenvector / np.linalg.norm(eigenvector, ord=1)
print(abs(normalised_eigenvector))
Finally! We now have a vector that verifies
, that is, a state of the system which is not changing anymore. Compare the values you just obtained with those you found by simulating the system for a finite number of steps. How do they relate?
## Nothing to code, discuss with your group :)
4.4.1. Simulation approach to finding equilibrium points#
Look at the equilibrium values that you obtained on your simulated SIRS model, write them as a state vector
using a numpy array and verify that this state vector is indeed a eigenvector of the system by verifying that
## Your code here
Experiment with a range of initial conditions in the infectious disease model to provide evidence that an equilibrium state is reached for all meaningful initial states.
## Code solution here.
Perform an analysis similar to the that in the example for the following infectious disease model. In this model the rate at which individuals move from the Recovered category to the Susceptible category is less than that in the example. Make a plot similar to that in the example and compute the theoretical equilibrium values for
, , and . Compare the results obtained with both approaches.
## Code solution here