In this session, you will use existing code in a public repo on GitHub that approximates the solution to the Lotka-Volterra equations.
You will make your own copy of the repo by forking it. By making a fork, you create a copy that you can work in separately.
The repo contains a single .Rmd (lotka-volterra.Rmd), with
information and code to numerically approximate solutions to the
Lotka-Volterra equations and make a graph.
Open the .Rmd and knit. We will talk through the code together. A couple
of things to note:
When you knit, the .html doesn’t appear in the git pane. Why?
How does the numerical approximation change if you update the initial conditions? The parameter estimates?
Try making changes, then stage > commit > push back to your remote repo. Make sure the changes appear there.
Congratulations! You’ve adding forking and cloning to your git toolkit.
Here, we’ll learn a few basic commands to talk with your computer through the command line to navigate around and do some git stuff outside of RStudio.
Some basics:
Now for git:
In this session, we’ll see how to use the deSolve::ode()
function to numerically estimate the solution to the Lotke-Volterra
equations for predator-prey populations.
First, we’ll learn a bit more about how numerical methods for approximating solutions to differential equations work.
Credit: This lesson is closely based on the article (Numerically solving differential equations with R)[https://rstudio-pubs-static.s3.amazonaws.com/32888_197d1a1896534397b67fb04e0d4899ae.html]
library(tidyverse)
library(deSolve)
library(kableExtra)
We’ll just do this once to reinforce how numerical integration generally works. Given the differential equation:
\[\frac{dP}{dt}=0.4P(0.5 - P)\]
and the initial condition \(P(0)=0.2\), approximate the solution \(P(t)\) at t = 1, 2, 3, 4, 5.
First, what is \(t\), \(P\), and \(\frac{dP}{dt}\) at \(t = 0\)? Here we have the initial condition, and can calculate the slope:
t | P | dfdx |
---|---|---|
0 | 0.2 | 0.024 |
Now, we approximate the value of \(P(t)\) and \(\frac{dP}{dt}\) at the next step (\(t = 1\)) as if the slope is continuing at 0.024. Which means the approximate value of \(P(1) = 0.2 + 0.024*(1) = 0.224\). At that value of \(P\), the slope is approximated by \(\frac{dP}{dt}=0.4*0.224(0.5 - 0.224) = 0.02473\).
So now, our table is expand to:
t | P | dfdx |
---|---|---|
0 | 0.200 | 0.02400 |
1 | 0.224 | 0.02473 |
Then we continue. What do we approximate is the value of P(t) at t = 2? Using the new slope of 0.01056, the approximate value of \(P(2) = 0.224 + 0.02473*1 = 0.24873\). Using that value, we approximate the slope at \(t=2\) by \(\frac{dP}{dt}=0.4*0.24873(0.5 - 0.24873) = 0.024999\).
Our table is then updated to:
t | P | dfdx |
---|---|---|
0 | 0.20000 | 0.024000 |
1 | 0.22400 | 0.024730 |
2 | 0.24873 | 0.024999 |
And we could continue doing that for many incremental values of \(t\) to get an approximate solution for what the function \(P(t)\) looks like. But that would be very tedious. Up next - computer help!
There are many different approaches to numerical approximation of solutions for differential equations. This example is meant to give you an idea of how the iterative approximation works generally.
Now, let’s see how much more quickly we can do this with some computer power!
deSolve::ode()
Allison update (2021-08-03): For time, students will FORK my repo (https://github.com/allisonhorst/eds212-comp-3a), then we’ll talk through the code. Lotke-Volterra example shown below is the same that students will run from that repo.
Let’s say we have a system modeled by:
\[\frac{dC}{dt}=\lambda
C^2-3.1\delta\] and we don’t know how to solve this analytically.
Let’s use deSolve
in R to help us find a solution
numerically, given an initial condition at \(C(0)=4.8\) and for parameter values \(\lambda = 0.4\) and \(\delta=0.06\).
Here’s what a numeric solution looks like with
deSolve
:
First, we’ll make a time sequence that we want to estimate numerical solutions over:
# Create a time sequence:
time_seq <- seq(from = 0, to = 0.2, by = 0.001)
Then, we’ll store the parameter(s) and initial conditions:
# Set some parameter values (these can change - keep it in mind):
parameter_example <- c(lambda = 0.4, delta = 0.06)
# Set the initial condition (prey and predator populations at t = 0).
# Recall: x = prey, y = predator
init_cond_example <- c(C = 4.8)
Store the differential equation as a function:
# Prepare the series of differential equations as a function:
df_equation_example <- function(time_seq, init_cond_example, parameter_example) {
with(as.list(c(init_cond_example, parameter_example)), {
dcdt = lambda * C ^ 2 - 3.1 * delta
return(list(c(dcdt)))
})
}
Use deSolve::ode()
to approximate the solution
numerically:
# Find the approximate the solution using `deSolve::ode()`:
approx_df_example <- ode(y = init_cond_example, times = time_seq, func = df_equation_example, parms = parameter_example)
# Check the class:
class(approx_df_example)
## [1] "deSolve" "matrix"
# We really want this to be a data frame:
approx_df_example <- data.frame(approx_df_example)
# Plot it!
ggplot(data = approx_df_example, aes(x = time, y = C)) +
geom_point(size = 0.1)
Now let’s try it for something a bit more interesting! As described in the lecture, the Lotke-Volterra models have been used to describe predator-prey populations.
\[\frac{dx}{dt}=\alpha x-\beta xy\]
From Wikipedia: “The prey are assumed to have an unlimited food supply and to reproduce exponentially, unless subject to predation; this exponential growth is represented in the equation above by the term \(\alpha x\). The rate of predation upon the prey is assumed to be proportional to the rate at which the predators and the prey meet, this is represented above by \(\beta xy\). If either x or y is zero, then there can be no predation.”
\[\frac{dy}{dt}=\delta xy - \gamma y\]
From Wikipedia: “In this equation, \(\delta xy\) represents the growth of the predator population. (Note the similarity to the predation rate; however, a different constant is used, as the rate at which the predator population grows is not necessarily equal to the rate at which it consumes the prey). The term \(\gamma y\) represents the loss rate of the predators due to either natural death or emigration, it leads to an exponential decay in the absence of prey.
Where:
To find an approximate solution in R, we will need four things: - Parameter values - A sequence of times over which we’ll approximate predator & prey populations - An initial condition (initial populations of predator & prey at t = 0) - The differential equations that need to be solved
Solving the Lotke-Volterra equation:
# Create a sequence of times (days):
time <- seq(0, 25, by = 0.05)
# Set some parameter values (these can change - keep it in mind):
parameters <- c(alpha = .75, beta = 0.8, delta = 0.5, gamma = 1)
# Set the initial condition (prey and predator populations at t = 0).
# Recall: x = prey, y = predator
init_cond <- c(x = 5, y = 2)
# Prepare the series of differential equations as a function:
lk_equations <- function(time, init_cond, parameters) {
with(as.list(c(init_cond, parameters)), {
dxdt = alpha * x - beta * x * y
dydt = delta * x * y - gamma * y
return(list(c(dxdt, dydt)))
})
}
# Find the approximate the solution using `deSolve::ode()`:
approx_lk <- ode(y = init_cond, times = time, func = lk_equations, parms = parameters)
# Check the class:
class(approx_lk)
## [1] "deSolve" "matrix"
# We really want this to be a data frame, and we want both prey (x) and predator (y) to be in the same column -- we'll learn why in EDS 221 (tidy data)
approx_lk_df <- data.frame(approx_lk) %>%
pivot_longer(cols = c(x,y), names_to = "species", values_to = "population")
# Plot it!
ggplot(data = approx_lk_df, aes(x = time, y = population)) +
geom_line(aes(color = species))
What happens as you change the different parameters (and re-run the entire code chunk)? How does that align with what you see in the graph? Some things to keep in mind: