I'm writing a agent-based model for phytoplankton. Right now it is fairly basic and includes functions for growth, uptake of a resource ("S" in the code), division, mortality, and a function to keep track of the total extracellular resource concentration outside of the cells (q represents the amount of resource in an individual, then I sum the q of all the individuals and subtract it from the total S). It has become quite a time hog from all the for loops taking more than 24 hours to run when it should be much faster. Are there ways that I can optimize my code to help it run faster?
uptake <- function(x){
vmax <- x[1]
km <- x[2]
S <- x[3]
result <- vmax*(S/(km+S))
return(result)
}
growth <- function(x){
mumax <- x[1]
qnaught <- x[2]
q <- x[3]
result <- mumax*(1-(qnaught/q))
return(result)
}
division <- function(x){
mu <-x[1]
m <- x[2]
result <- mu*m
return(result)
}
extracellular <- function(x){
S <- x[1]
flow_rate <- x[2]
nutrient_inflow <- x[3]
quota_per_time <- x[4]
result <- ((flow_rate*nutrient_inflow)-(flow_rate*S)-quota_per_time)
return(result)
}
## Initial agents and time step parameters
num_agents <- 200
num_time_steps <- 400
## Set up data frame for agents
agents <- data.frame("ID" = 1:num_agents, "vmax" = 0.0000014, "km" = 17,
"mumax" = 1.1, "qnaught" = 0.00000036, "cell_size" = 0.00075,
"alive" = 1)
## Set up data frame for state variables
state <- data.frame("S" = 1.2)
agents <- cbind(agents, state)
state$flow_rate <- 0.86
state$nutrient_inflow <- 1.4
## Set up other parameter values
## q represents the amount of resource in an individual
q <- rep(0, num_agents)
size <- rep(0, num_agents)
m0 <- 0.00075
output <- data.frame()
agents_time <- data.frame()
## For loop for model
for (i in 1:num_time_steps) {
## mortality of agents in each time step
for (k in agents$ID){
random <- runif(1, min = 1, max = 100)
if (random < 10.0){
agents[k,7] = 0
}
}
agents_alive <- filter(agents, alive == 1)
## update S from state data frame
agents_alive$S <- state$S
q <- q[1:nrow(agents_alive)]
## sum uptake per time step to use for extracellular concentration
quota_per_agent <- apply(agents_alive[, c(2, 3, 8)], MARGIN = 1, FUN = function(x) uptake(x))
quota_per_time <- sum(quota_per_agent)
q <- q + quota_per_agent
tmp <- cbind(agents_alive, q)
mu <- apply(tmp[, c(4, 5, 9)], MARGIN = 1, FUN = function(x) growth(x))
tmp <- cbind(tmp, mu)
size <- size[1:nrow(agents_alive)]
size <- size + apply(tmp[, c(6, 10)], MARGIN = 1, FUN = function(x) division(x))
## if size calculated by division function is below minimum cell size (0.75),
## replace with minimum cell size
size <- ifelse(size < 0.00075, 0.00075, size)
index = 1
## division by cell size
for (j in size){
if (j > (2*tmp[index,6])){
size[index] = m0
q[index] = (q[index]/2)
last <- tail(agents$ID, n = 1)
new <- c(last + 1, 0.0000014, 17,
1.1, 0.00000036, m0, 1, state$S)
agents <- rbind(agents, new)
agents_alive <- rbind(agents_alive, new)
q <- append(q, q[index])
mu <- append(mu, mu[index])
size <- append(size, size[index])
}
index = index + 1
}
## update extracellular nutrient concentration state variable
state$quota_per_time <- quota_per_time
new_S <- state$S + apply(state, MARGIN = 1, FUN = function(x) extracellular(x))
state <- replace(state, 1, new_S)
if (state$S < 0){
state$S <- 0
}
#dataframe of outputs for each time step
out_per_time <- data.frame("ID" = agents_alive$ID,
"vmax" = agents_alive$vmax,
"km" = agents_alive$km,
"S" = agents_alive$S,
"mumax" = agents_alive$mumax,
"qnaught" = agents_alive$qnaught,
"time" = rep(i, nrow(agents_alive)),
"q" = q,
"mu" = mu,
"cell_size" = size,
"alive" = agents_alive$alive)
#row-append to output dataframe that stores outputs for all agents and all time steps
output <- rbind(output, out_per_time)
}