英文:
Why does my deSolve model in R stop integrating when I incorporate a conditional source of mortality in my population model?
问题
我已经在R中构建了一个人口模型,以识别在我的公寓中控制蟑螂数量的潜在策略的有效性。我引入了一个术语,当成年蟑螂群体遇到我设置的陷阱时,它将为其增加额外的死亡来源。我通过一个if-else语句将这个术语引入模型,只允许在成年人口(分为怀孕和非怀孕人口)超过100个个体的阈值时才发生额外的死亡。我将这定义为条件死亡来源,因为我认为我部署陷阱的倾向将取决于我首先检测到蟑螂的能力,这将是其成年人口规模的函数。然而,当我将这个条件死亡来源写入我的ODE系列并应用deSolve包时,模型在满足条件时停止积分。
以下是我的模型结构、起始条件、参数和输出的R代码:
library(tidyr)
library(deSolve)
#模型:
cockpop.t <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
dA <- ((lambda * N) + (theta * P) - (phi * A * (1 - (A+P)/K)) - (mua * A) - (if(A+P < det) {0}
else(muta*A)))
dP <- ((phi * A * (1 - (A+P)/K)) - (theta * P) - (if(A+P < det) {0}
else(muta*P)))
dE <- (40*((theta * P) - (gamma * E) - (mue * E)))
dN <- ((gamma * E) - (lambda * N) - (mun * N))
return(list(c(dA, dP, dE, dN)))
})
}
times <- seq(0, 1000, by = 1)
#时间
parameters.t <- c(lambda = 0.007142857, theta = 0.03571429, phi = 0.07142857, mua = 0.004761905, gamma = 0.04, mue = 0.004, mun = 0.0007, K = 1000, muta = 0.05, det=50)
#模型参数
initial.A <- 9
initial.P <- 1
initial.E <- 0
initial.N <- 0
initial.values <- c(A = initial.A, P = initial.P, E = initial.E, N = initial.N)
#初始条件
output.t<- as.data.frame(ode(func = cockpop.t, y = initial.values, parms = parameters.t, times = times)) %>%
pivot_longer(!time, names_to="state",values_to="population")
#模型输出
参数'det'是我对成年人口的检测阈值(当前设置为100个个体)。一旦我定义了初始人口大小和参数,模型就会运行到大约第200个时间步,此时A + P == 100,并且模型无法继续积分。如果我从A或P中删除条件性死亡来源,而在另一个中保留它,模型就能够成功积分。是什么导致了这个错误,我如何修改我的代码来解决它?
我尝试将条件语句重写为ifelse()参数,假设在上面的写法中可能存在错误,但这并没有解决问题。我还尝试以不同方式定义语句,通过构建一个单独的人口类T,来跟踪A和P人口的综合,并在条件性死亡语句中使用T而不是(A+P)项。我认为,可能会在与导致其各自类大小变化有关的条件语句中结合这两个类大小可能会导致问题,但这也没有解决问题。
谢谢您的帮助!
英文:
I have constructed a population model in R to identify the efficacy of potential control strategies for the cockroach population in my apartment. I have introduced a term that will add an additional source of mortality to the adult cockroach population when they encounter traps I have set. I introduce this term into the model with an if-else statement, only allowing the additional source of mortality to occur once the adult population (broken into a pregnant and non-pregnant population) has crossed a threshold of 100 individuals. I defined this as a conditional source of mortality because I believe my propensity to deploy traps will depend on my ability to detect the cockroaches in the first place, which will be a function of their adult population size. However, when I write this conditional source of mortality into my series of ODE's and apply the deSolve package, the model integration stops when the condition is met.
Below is my R code for the model structure, starting conditions, parameters, and output:
library(tidyr)
library(deSolve)
#The model:
cockpop.t <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
dA <- ((lambda * N) + (theta * P) - (phi * A * (1 - (A+P)/K)) - (mua * A) - (if(A+P < det) {0}
else(muta*A)))
dP <- ((phi * A * (1 - (A+P)/K)) - (theta * P) - (if(A+P < det) {0}
else(muta*P)))
dE <- (40*((theta * P) - (gamma * E) - (mue * E)))
dN <- ((gamma * E) - (lambda * N) - (mun * N))
return(list(c(dA, dP, dE, dN)))
})
}
times <- seq(0, 1000, by = 1)
#Times
parameters.t <- c(lambda = 0.007142857, theta = 0.03571429, phi = 0.07142857, mua = 0.004761905, gamma = 0.04, mue = 0.004, mun = 0.0007, K = 1000, muta = 0.05, det=50)
#Model parameters
initial.A <- 9
initial.P <- 1
initial.E <- 0
initial.N <- 0
initial.values <- c(A = initial.A, P = initial.P, E = initial.E, N = initial.N)
#Initial conditions
output.t<- as.data.frame(ode(func = cockpop.t, y = initial.values, parms = parameters.t, times = times)) %>%
pivot_longer(!time, names_to="state",values_to="population")
#Model output
The parameter 'det' is my detection threshold for the adult population (currently set at 100 individuals). Once I define the initial population sizes and parameters, the model runs until about the 200th timestep, at which point A + P == 100, and the model fails to integrate further. If I remove the conditional source of mortality from either A or P and leave it in the other, the model successfully integrates. What is causing this error and how might I modify my code to address it?
I tried re-writing the conditional statement as an ifelse() argument, assuming something might be written incorrectly in the way it was written above, but that did not fix the issue. I also tried defining the statement differently by constructing a separate population class, T, that kept track of the combined A and P population and using T in the conditional mortality statement instead of the (A+P) term. I thought that potentially incorporating the two class sizes in the conditional statement that contributes to the change in their respective class sizes may have caused issues, but that also did not solve the issue.
Thank you for your help!
答案1
得分: 3
你的控制函数的急剧开/关特性使得集成变得困难,系统设置方式导致其频繁开启和关闭(这是从下面的结果中得出的结论,而不是仔细分析)。我尝试将集成切换到method = "rk4"
,这种方法不会尝试检测和处理刚性,但我不能保证结果在数学上是正确的。(也许对于你的目的来说足够好了。)你可以尝试更平滑的控制函数...
我将这些机制封装成一个函数,以便更方便地进行实验。
f <- function(tmax, method = "lsoda") {
output.t <- ode(func = cockpop.t, y = initial.values,
parms = parameters.t, times = 0:tmax, method = method) %>%
as.data.frame() %>%
tidyr::pivot_longer(-time, names_to="state",values_to="population") %>%
ggplot(aes(time, population, colour = state)) + geom_line()
}
f(600)
只运行到大约t=285,正如你提到的一样。
print(gg1 <- f(600, method = "rk4"))
放大查看:
gg1 + coord_cartesian(xlim = c(250, 350), ylim = c(15, 35))
英文:
The sharp on/off nature of your control function makes it hard to integrate — the way your system is set up makes it cycle on and off frequently (this is a conclusion from looking at the results below, not a careful analysis). I was able to get the integration to work, sort of, by switching to method = "rk4"
which doesn't try to detect and handle stiffness, but I would not guarantee that the results are mathematically sound. (They may well be good enough for your purpose.) You could try a smoother control function ...
I wrapped the machinery in a function for more convenient experimentation.
f <- function(tmax, method = "lsoda") {
output.t <- ode(func = cockpop.t, y = initial.values,
parms = parameters.t, times = 0:tmax, method = method) |>
as.data.frame() |>
tidyr::pivot_longer(-time, names_to="state",values_to="population") |>
ggplot(aes(time, population, colour = state)) + geom_line()
}
f(600)
only makes it to to about t=285, as you mentioned.
print(gg1 <- f(600, method = "rk4"))
Zoom in:
gg1 + coord_cartesian(xlim = c(250, 350), ylim = c(15, 35))
答案2
得分: 1
这是一种使用lsoda
的另一种方法。与使用if
条件限制不同,可以使用平滑的条件死亡率启动。可以使用不同的饱和函数,例如从零到一的Monod函数。
提供的解决方案不仅在数值上稳定且更精确,而且速度更快。
library(deSolve)
## 带有if()的原始模型
cockpop.t <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
dA <- ((lambda * N) + (theta * P) - (phi * A * (1 - (A + P)/K)) -
(mua * A) - (if(A + P < det) {0} else(muta * A)))
dP <- ((phi * A * (1 - (A+P)/K)) - (theta * P) -
(if(A + P < det) {0} else(muta * P)))
dE <- (40*((theta * P) - (gamma * E) - (mue * E)))
dN <- ((gamma * E) - (lambda * N) - (mun * N))
return(list(c(dA, dP, dE, dN)))
})
}
## 具有平滑过渡的修改后的模型
cockpop.t2 <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
## 使用Monod函数,从零平滑增加到一
x <- max(0, A + P - det)
limiter <- x / (Km + x)
dA <- lambda * N + theta * P - phi * A * (1 - (A + P) / K) -
mua * A - muta * A * limiter
dP <- (phi * A * (1 - (A + P) / K)) - theta * P -
muta * P * limiter
dE <- 40 * (theta * P - gamma * E - mue * E)
dN <- gamma * E - lambda * N - mun * N
list(c(dA, dP, dE, dN))
})
}
times <- seq(0, 1000, by = 1)
parameters.t <- c(lambda = 0.007142857, theta = 0.03571429,
phi = 0.07142857, mua = 0.004761905,
gamma = 0.04, mue = 0.004, mun = 0.0007,
K = 1000, muta = 0.05, det=50, Km = 0.1)
## Monod常数Km并不重要。它应该足够小,以便在A + P >= det后尽早开始,但不要太小,以避免数值不稳定性。
initial.values <- c(A = 0, P = 1, E = 0, N = 0)
## Ben Bolker的rk4解决方案
system.time(
o1 <- ode(func = cockpop.t, y = initial.values, parms = parameters.t,
times = times, method = "rk4")
)
# user system elapsed
# 0.09 0.00 0.09
## tpetzoldt的lsoda方法
system.time(
o2 <- ode(func = cockpop.t2, y = initial.values, parms = parameters.t,
times = times, method = "lsoda")
)
# user system elapsed
# 0.06 0.00 0.06
plot(o1, o2)
legend("topleft", c("if with rk4", "smooth transition"),
lty = 1:2, col = 1:2)
除了使用ggplot
外,还可以使用deSolve的内置绘图函数,以直接比较不同的情景。
英文:
Here another approach that works also with lsoda
. Instead of a hard limit with an if
-construction, one can use a smooth onset of the "conditional mortality". Different saturation functions can be used here, e.g. a Monod-term that goes from zero to one.
The provided solution is not only numerically stable and more precise, but also faster.
library(deSolve)
## original model with if()
cockpop.t <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
dA <- ((lambda * N) + (theta * P) - (phi * A * (1 - (A + P)/K)) -
(mua * A) - (if(A + P < det) {0} else(muta * A)))
dP <- ((phi * A * (1 - (A+P)/K)) - (theta * P) -
(if(A + P < det) {0} else(muta * P)))
dE <- (40*((theta * P) - (gamma * E) - (mue * E)))
dN <- ((gamma * E) - (lambda * N) - (mun * N))
return(list(c(dA, dP, dE, dN)))
})
}
## modified model with smooth transition
cockpop.t2 <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
## use a Monod-term, smoothly increasing from zero to one
x <- max(0, A + P - det)
limiter <- x / (Km + x)
dA <- lambda * N + theta * P - phi * A * (1 - (A + P) / K) -
mua * A - muta * A * limiter
dP <- (phi * A * (1 - (A + P) / K)) - theta * P -
muta * P * limiter
dE <- 40 * (theta * P - gamma * E - mue * E)
dN <- gamma * E - lambda * N - mun * N
list(c(dA, dP, dE, dN))
})
}
times <- seq(0, 1000, by = 1)
parameters.t <- c(lambda = 0.007142857, theta = 0.03571429,
phi = 0.07142857, mua = 0.004761905,
gamma = 0.04, mue = 0.004, mun = 0.0007,
K = 1000, muta = 0.05, det=50, Km = 0.1)
## The Monod constant Km is uncritical. It should be small enough
## to start early after A + P >= det, but not too small to avoid
## numerical instability.
initial.values <- c(A = 0, P = 1, E = 0, N = 0)
## Ben Bolker's solution with rk4
system.time(
o1 <- ode(func = cockpop.t, y = initial.values, parms = parameters.t,
times = times, method = "rk4")
)
# user system elapsed
# 0.09 0.00 0.09
## tpetzoldt's approach with model reformulation
system.time(
o2 <- ode(func = cockpop.t2, y = initial.values, parms = parameters.t,
times = times, method = "lsoda")
)
# user system elapsed
# 0.06 0.00 0.06
plot(o1, o2)
legend("topleft", c("if with rk4", "smooth transition"),
lty = 1:2, col = 1:2)
Instead of ggplot
one can also use the built-in plot function of deSolve, that allows direct comparison of scenarios.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论