在不使用根的情况下满足特定条件时过早地停止 R 的 deSolve 中的集成
Prematurely stopping integration in R's deSolve when certain condition is met without using roots
我知道 R 的 deSolve 中的提前终止可以通过使用根函数而不提供事件函数来实现,这将导致在找到根时终止积分。但是,通过使用此过程,我们仅限于应用具有求根功能的求解器。
事实上,我正在处理一个根的确切位置无关紧要的问题。我需要引入状态变量的突然变化,但发生这种情况的确切时刻并不重要。所以我可以在满足条件时停止积分,重新计算引入突然变化的新起始状态向量,然后重新开始积分。这仍然可以让我灵活地使用 deSolve 包提供的许多解算器中的任何一个。
有推荐的方法吗?
编辑
让我们考虑以下简化示例。所表示的系统是在一维中以 1 的恒定速度运动的对象。该对象从位置 x=0 开始,并沿维度的正方向移动。我们的目标是执行坐标原点的更改,例如当对象距离原点 10 或更高时,该位置参考 x = 10 的点。可以简化为此时的位置减去10
使用根,可以实现如下:
library(deSolve)
odeModel <- function(t, state, parameters) {
with(as.list(c(state, parameters)), {
x <- state
dx <- 1
list(
dx
)
})
}
initialState <- 0
times <- 0:15
rootFunc <- function(t, state, parameters) {
return(state-10)
}
eventFunc <- function(t, state, parameters) {
return(state - 10)
}
integrationTest <- ode(y=initialState, times=times, func=odeModel, parms=NULL,
events=list(func=eventFunc, root=TRUE), rootfun=rootFunc)
但是,出于上述原因,我正在尝试不使用 root 来执行此操作。可以通过在输出中包含一些变量(必须是数字,否则 deSolve 不会接受它作为每个评估时间的输出)来跟踪条件是否已满足,然后检查积分结果确定何时满足条件,应用所需的更改,从该点重新进行集成,然后合并输出。使用与之前相同的示例:
library(deSolve)
distanceHigherThan10 <- function(x) {
result <- if(x >= 10) 1 else 0
return(result)
}
odeModel <- function(t, state, parameters) {
with(as.list(c(state, parameters)), {
x <- state
dx <- 1
higherThanThreshold <- distanceHigherThan10(x)
list(
dx, higherThanThreshold
)
})
}
initialState <- 0
times <- 0:15
integration <- ode(y=initialState, times=times, func=odeModel, parms=NULL)
changePoint <- which(diff(integration[,3]) != 0)[1] + 1
newIntegrationTimes <- times[changePoint:length(times)] - times[changePoint]
newStartingPosition <- integration[changePoint, 2] - 10
newIntegration <- ode(y=newStartingPosition, times=newIntegrationTimes, func=odeModel, parms=NULL)
newIntegration[, 1] <- newIntegration[, 1] + times[changePoint]
newIntegration[, 3] <- 1
combinedResults <- rbind(integration[1:(changePoint-1), ], newIntegration)
然而,这会导致无用的计算,因为 time=10 之后的积分执行了两次。满足条件后直接停止第一次积分,然后直接进入第二次积分部分,效率会更高。这就是为什么我问是否有任何方法可以在满足特定条件时停止集成,返回到该点的结果
首先,deSolve 的几个求解器支持求根,table 可以在包 vignettes 或 here.
中找到
在其他情况下,总是可以将 ode
嵌入到自己的函数中。这是一种支持任何求解器的可能方法,它是在 OP 提供代码示例之前编写的。它使用了一种相当通用的方法,因此应该可以根据具体问题对其进行调整。这个想法是 运行 求解器在循环中处于“stop-and-go”模式,其中积分的结果用作下一个时间步长的初始值。
library(deSolve)
## a model with two states
model <- function(t, y, p) {
list(c(p[1] * y[1] - p[2], p[1] * y[2]))
}
times <- 0:10
p <- c(-0.1, 0.1)
y0 <- c(y1 = 1, y2 = 2)
## standard approach without root detection
out1 <- ode(y = y0, times = times, model, p = p)
out1
## stepper function that stops after root finding
stepping <- function(y0, times, model, p, ...) {
for (i in 2:length(times)) {
o <- ode(y = y0, times[c(i-1, i)], func = model, p = p, ...)
if (i == 2) {
out <- o[1,]
}
## condition may be adapted
if (any(o[1, -1] * o[2, -1] < 0)) break
y0 <- o[2, -1]
out <- rbind(out, o[2,])
}
out
}
out2 <- stepping(y0, times, model, p)
out1 # all time steps
out2 # stopped at root
我知道 R 的 deSolve 中的提前终止可以通过使用根函数而不提供事件函数来实现,这将导致在找到根时终止积分。但是,通过使用此过程,我们仅限于应用具有求根功能的求解器。
事实上,我正在处理一个根的确切位置无关紧要的问题。我需要引入状态变量的突然变化,但发生这种情况的确切时刻并不重要。所以我可以在满足条件时停止积分,重新计算引入突然变化的新起始状态向量,然后重新开始积分。这仍然可以让我灵活地使用 deSolve 包提供的许多解算器中的任何一个。
有推荐的方法吗?
编辑
让我们考虑以下简化示例。所表示的系统是在一维中以 1 的恒定速度运动的对象。该对象从位置 x=0 开始,并沿维度的正方向移动。我们的目标是执行坐标原点的更改,例如当对象距离原点 10 或更高时,该位置参考 x = 10 的点。可以简化为此时的位置减去10
使用根,可以实现如下:
library(deSolve)
odeModel <- function(t, state, parameters) {
with(as.list(c(state, parameters)), {
x <- state
dx <- 1
list(
dx
)
})
}
initialState <- 0
times <- 0:15
rootFunc <- function(t, state, parameters) {
return(state-10)
}
eventFunc <- function(t, state, parameters) {
return(state - 10)
}
integrationTest <- ode(y=initialState, times=times, func=odeModel, parms=NULL,
events=list(func=eventFunc, root=TRUE), rootfun=rootFunc)
但是,出于上述原因,我正在尝试不使用 root 来执行此操作。可以通过在输出中包含一些变量(必须是数字,否则 deSolve 不会接受它作为每个评估时间的输出)来跟踪条件是否已满足,然后检查积分结果确定何时满足条件,应用所需的更改,从该点重新进行集成,然后合并输出。使用与之前相同的示例:
library(deSolve)
distanceHigherThan10 <- function(x) {
result <- if(x >= 10) 1 else 0
return(result)
}
odeModel <- function(t, state, parameters) {
with(as.list(c(state, parameters)), {
x <- state
dx <- 1
higherThanThreshold <- distanceHigherThan10(x)
list(
dx, higherThanThreshold
)
})
}
initialState <- 0
times <- 0:15
integration <- ode(y=initialState, times=times, func=odeModel, parms=NULL)
changePoint <- which(diff(integration[,3]) != 0)[1] + 1
newIntegrationTimes <- times[changePoint:length(times)] - times[changePoint]
newStartingPosition <- integration[changePoint, 2] - 10
newIntegration <- ode(y=newStartingPosition, times=newIntegrationTimes, func=odeModel, parms=NULL)
newIntegration[, 1] <- newIntegration[, 1] + times[changePoint]
newIntegration[, 3] <- 1
combinedResults <- rbind(integration[1:(changePoint-1), ], newIntegration)
然而,这会导致无用的计算,因为 time=10 之后的积分执行了两次。满足条件后直接停止第一次积分,然后直接进入第二次积分部分,效率会更高。这就是为什么我问是否有任何方法可以在满足特定条件时停止集成,返回到该点的结果
首先,deSolve 的几个求解器支持求根,table 可以在包 vignettes 或 here.
中找到在其他情况下,总是可以将 ode
嵌入到自己的函数中。这是一种支持任何求解器的可能方法,它是在 OP 提供代码示例之前编写的。它使用了一种相当通用的方法,因此应该可以根据具体问题对其进行调整。这个想法是 运行 求解器在循环中处于“stop-and-go”模式,其中积分的结果用作下一个时间步长的初始值。
library(deSolve)
## a model with two states
model <- function(t, y, p) {
list(c(p[1] * y[1] - p[2], p[1] * y[2]))
}
times <- 0:10
p <- c(-0.1, 0.1)
y0 <- c(y1 = 1, y2 = 2)
## standard approach without root detection
out1 <- ode(y = y0, times = times, model, p = p)
out1
## stepper function that stops after root finding
stepping <- function(y0, times, model, p, ...) {
for (i in 2:length(times)) {
o <- ode(y = y0, times[c(i-1, i)], func = model, p = p, ...)
if (i == 2) {
out <- o[1,]
}
## condition may be adapted
if (any(o[1, -1] * o[2, -1] < 0)) break
y0 <- o[2, -1]
out <- rbind(out, o[2,])
}
out
}
out2 <- stepping(y0, times, model, p)
out1 # all time steps
out2 # stopped at root