library(ggplot2)
library(dplyr)
library(magrittr)
library(tidyr)Integrating Lotka Volterra Model into the Gillespie Algorithm
First, we visualize the model Lotka-Volterra Model
\[ \begin{cases} \frac{dx}{dt} = ax - bxy \\ \frac{dy}{dt} = dxy - cy \end{cases} \]
We sum all the terms contained in the model: \[ A = ax + bxy + dxy + cy \]
We assign values that together form a whole, called reactions \[ Rtotal = R1+R2+R3+R4 \]
We determine the reactions
\[X (ax) x + 1\] \[X (bxy) x - 1 \] \[Y(dxy) y +1\] \[Y(cy) y - 1 \]
Prey birth \[ax/A \] Death of prey by predation \[ bxy/A \] Conversion of prey to predator \[dxy/A \] Natural death of predator \[ cy/A \]
Implementing the Gillespie algorithm for the Lotka-Volterra model.
The necessary library
Creating the function, initialization state, sum of reactions and data frame to store the information
LotkaVolterraGillespie <- function(a, b, c, d, X0, Y0, t_end) {
# Initialize state
X <- X0
Y <- Y0
t <- 0
all.reactions <- a+b+c+d
dataPP <- data.frame(Time = c(t),
Prey = c(X),
Predator = c(Y))
# Lotka-Volterra into Gillespie simulation loop
while (t < t_end && X > 0 && Y > 0) {
R1 <- a * X # Prey birth
R2 <- b * X * Y # Prey death by predation
R3 <- d * X * Y # Predator birth by predation
R4 <- c * Y # Predator natural death
R_total <- R1 + R2 + R3 + R4 # The sum of the reactions
if (R_total == 0) break # If there is no more interactions
tau <- rexp(1, R_total) # Generating time until the next event
event <- runif(1, 0, R_total) # Choosing which reaction will occur
# When which event will occur?
if (event < R1) {
X <- X + 1 # Prey birth
} else
if (event < R1 + R2) {
X <- X - 1 # Prey death by predation
} else
if (event < R1 + R2 + R3) {
Y <- Y + 1 # Predator birth by predation
} else {
Y <- Y - 1 # Predator natural death
}
t <- t + tau # Updating time
dataPP <- rbind(dataPP, c(t, X, Y)) # Storing results
}
return(dataPP)
}Choosing the parameters
set.seed(124) # random number sequence
a = 1.0 # maximum prey birth
b = 0.02 # low predation
c = 1.0 # maximum predator birth
d = 0.01 # low natural death
X0 = 50 # initial number of prey
Y0 = 20 # initial number of predator
t_end = 100 # when the time stops
simulation <- LotkaVolterraGillespie(a, b, c, d, X0, Y0, t_end) # same parameters
simulation1 <- LotkaVolterraGillespie(a, b, c, d, X0, Y0, t_end) # same parametersPlotting simulation
plot1 <- ggplot(simulation, aes(x = Time)) +
geom_point(aes(y = Prey, color = "Prey"), size = 2) +
geom_point(aes(y = Predator, color = "Predator"), size = 2) +
labs(title = "Lotka-Volterra in Gillespie Simulation by Amanda Loureiro",
x = "Time", y = "Population") +
scale_color_manual(values = c("#caf085", "#FFC0CB"),
name = "Animals",
labels = c("Prey", "Predator")) +
theme_minimal()+
theme(
legend.position = "bottom")So far, we have implemented the Lotka-Volterra model in the Gillespie algorithm, but let’s continue and explore the results.
Viewing the plot of the first simulation
In this graph, it is possible to see that populations become extinct before the end time. However, even before they become extinct, they fluctuate equally, but with the predator population always greater than the prey population.
Plotting the second simulation
plot2 <- ggplot(simulation1, aes(x = Time)) +
geom_point(aes(y = Prey, color = "Prey"), size = 2) +
geom_point(aes(y = Predator, color = "Predator"), size = 2) +
labs(title = "Lotka-Volterra in Gillespie Simulation by Amanda Loureiro",
x = "Time", y = "Population") +
scale_color_manual(values = c("#caf085", "#FFC0CB"),
name = "Animals",
labels = c("Prey", "Predator")) +
theme_minimal()+
theme(
legend.position = "bottom",
legend.text = element_text(size = 12, face = "italic")+
guides(fill = guide_legend(override.aes = list(size = 5))))Viewing the plot of the second simulation
We have the same observations as the previous graph, except that extinction occurs much earlier.
Plotting the combination of two simulations
combination <- ggplot() +
geom_point(data = simulation, aes(x = Time, y = Prey), color = "#66CDAA") +
geom_point(data = simulation1, aes(x = Time, y = Prey), color = "#caf085")+
geom_point(data = simulation1, aes(x = Time, y = Predator), color = "#DE3163")+
geom_point(data = simulation, aes(x = Time, y = Predator), color = "#FFC0CB")+
labs(title = "Stochastic Lotka-Volterra Model (Gillespie Algorithm)", x = "Time", y = "Population Size") +
theme_minimal()Prey in shades of green Predator in shades of pink
Viewing the plot of the two combined simulations
Just a way to see that even with the same parameters, two simulations can be totally different due to stochasticity.
Multiple replications (Monte Carlo)
So far, we have done 1 simulation. But what if we did countless simulations? To do this, we use the Monte Carlo method, with multiple replications.
# Defining parameters and initial conditions
a <- 0.3 # Low prey growth
b <- 0.02 # Very low death by predation
c <- 0.3 # Low predator reproduction rate
d <- 0.01 # Very low predator mortality rate
X0 <- 10 # Initial prey population size
Y0 <- 10 # Initial predator population size
t_end <- 200 # Simulation time
set.seed(123) # Random number sequence
ntimes <- 100 # Number of simulations
final_pop_prey <- c()
final_pop_pred <- c()
extinctions_prey <- 0
extinctions_predator <- 0
simulations_list <- list()Running the simulations
for (i in 1:ntimes) {
nsimulations <- LotkaVolterraGillespie(a, b, c, d, X0, Y0, t_end)
nsimulations$Simulation <- i
simulations_list[[i]] <- nsimulations
# Capturing final population sizes
final_pop_prey[i] <- nsimulations$Prey[nrow(nsimulations)] # Final prey population
final_pop_pred[i] <- nsimulations$Predator[nrow(nsimulations)] # Final predator population
# Checking for extinction (if population reaches zero)
if (final_pop_prey[i] == 0) {
extinctions_prey <- extinctions_prey + 1
}
if (final_pop_pred[i] == 0) {
extinctions_predator <- extinctions_predator + 1
}
}Viewing the average of the simulations
all_simulations <- bind_rows(simulations_list)
time_grid <- seq(0, t_end, by = 1)
interpolated_data <- all_simulations %>%
group_by(Simulation) %>%
complete(Time = time_grid) %>%
fill(Prey, Predator, .direction = "down")
mean_data <- interpolated_data %>%
group_by(Time) %>%
summarize(MeanPrey = mean(Prey, na.rm = TRUE),
MeanPredator = mean(Predator, na.rm = TRUE))Plotting average over time
ggplot(mean_data, aes(x = Time)) +
geom_line(aes(y = MeanPrey, color = "Prey"), size=1) +
geom_line(aes(y = MeanPredator, color = "Predator"), size=1) +
labs(title = "Simulations Averages", x = "Time", y = "Average Population") +
scale_color_manual(values = c("Prey" = "#caf085", "Predator" = "#DE3163"),
name = "Animals") +
theme_minimal()Here we can see the average of several simulations with different parameters than the previous ones. The joint oscillation continues, but with the number of prey greater than the number of predators. However, as in the previous simulations, coexistence is not eternal and extinction occurs before the end time.
Calculating extinction probabilities
extinction_prob_prey <- extinctions_prey / ntimes
extinction_prob_predator <- extinctions_predator / ntimesOutputting the extinction probabilities
cat("Extinction probability for prey:", extinction_prob_prey, "\n")Extinction probability for prey: 0.4
cat("Extinction probability for predator:", extinction_prob_predator, "\n")Extinction probability for predator: 0.6
In this scenario, the probability of extinction of predators is higher than that of prey.
Histogram for final prey population
df_prey <- data.frame(Final_Population = final_pop_prey)
plot.prey <- ggplot(df_prey, aes(x = factor(Final_Population))) +
geom_bar(fill = "#caf085", alpha = 0.7) +
labs(title = "Distribution of Final Prey Populations", x = "Final Prey Population", y = "Frequency") +
theme_minimal() +
theme(
panel.grid.major = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
scale_x_discrete(breaks = seq(1, length(unique(df_prey$Final_Population)), by = 2))Histogram for final predator population
Only to realize that, in the end, most individuals in the final populations are zero, indicating the probability of extinction after all the simulations.