Установка начальных (состояний) значений для системы ODE в скомпилированной модели (deSolve, Rcpp)
Я борюсь с, вероятно, незначительной проблемой, вызывая скомпилированные ODE для решения с помощью пакета R, и я обращаюсь за советом к более опытным пользователям.
Задний план
Мне нужно решить пару систем ODE. Я определил ODE в отдельных функциях C++ (по одной для каждой модели), которые я вызываю через R вместе с. Начальные значения системы изменяются, если функция принимает входные данные от другой модели (так что в основном это каскад).
Однако это работает довольно хорошо, для одной модели мне нужно установить начальные параметры. Я пытался сделать это в функции C++, но похоже, что это не работает.
Пример работающего кода
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export("set_ODE")]]
SEXP set_ODE(double t, NumericVector state, NumericVector parameters) {
List dn(3);
double tau2 = parameters["tau2"];
double Ae2_4 = parameters["Ae2_4"];
double d2 = parameters["d2"];
double N2 = parameters["N2"];
double n2 = state["n2"];
double m4 = state["m4"];
double ne = state["ne"];
// change starting conditions for t < 2
if(t < 2) {
n2 = (n2 * m4) / N2;
m4 = n2;
ne = 0;
}
dn[0] = n2*d2 - ne*Ae2_4 - ne/tau2;
dn[1] = ne/tau2 - n2*d2;
dn[2] = -ne*Ae2_4;
return(Rcpp::List::create(dn));
}
/*** R
state <- c(ne = 10, n2 = 0, m4 = 0)
parameters <- c(N2 = 5e17, tau2 = 1e-8, Ae2_4 = 5e3, d2 = 0)
results <- deSolve::lsoda(
y = state,
times = 1:10,
func = set_ODE,
parms = parameters
)
print(results)
*/
Вывод читается (здесь только первые две строки):
time ne n2 m4
1 1 1.000000e+01 0.000000e+00 0.000000e+00
2 2 1.000000e+01 2.169236e-07 -1.084618e-11
На всякий случай: как запустить этот пример кода?
Мой пример был протестирован с использованием RStudio:
- Скопируйте код в файл с окончанием * .cpp
- Нажмите кнопку "Источник" (или
<shift>
+<cmd>
+<s>
)
Он должен работать и без RStudio, но пакеты
'Rcpp'
а также
'deSolve'
должен быть установлен, и для компиляции кода ему необходимы Rtools в Windows, компиляторы GNU в Linux и Xcode в macOS.
Проблема
Насколько я понимаю,
ne
должно быть
0
для
time = 1
(или же
t < 2
). К сожалению, решающая программа, похоже, не учитывает то, что я предоставил в функции C++, за исключением ODE. Если я изменю
state
в R на другое значение, однако это работает. Почему-то условие if, которое я определил в C++, игнорируется, но я не понимаю, почему и как я могу вычислить начальные значения в C++ вместо R.
1 ответ
Мне удалось воспроизвести ваш код. Мне кажется, что это действительно элегантно, даже если оно не использует всю мощь решателя. Причина в том, что Rcpp создает интерфейс для скомпилированной модели через обычную функцию R. Таким образом, обратные вызовы от слотов (например, lsoda) в R необходимы на каждом временном шаге. Такие обратные вызовы не подходят для "простого" интерфейса C/Fortran. Здесь связь между решателем и моделью происходит на уровне машинного кода.
С этой информацией я вижу, что нам не нужно ожидать проблем с инициализацией на уровне C / C ++, но это выглядит как типичный случай. Поскольку модельная функция - это просто производная модели (и только она). Интеграция осуществляется решателем «извне». Он вызывает модель всегда с фактическим состоянием интеграции, полученным из предыдущего временного шага (грубо говоря). Следовательно, невозможно принудительно установить переменные состояния на фиксированные значения в функции модели.
Однако есть несколько вариантов решения этой проблемы:
- цепочка вызовов lsoda
- использование событий
Ниже показан цепной подход, но я еще не уверен в инициализации параметров в первом временном сегменте, поэтому это может быть только частью решения.
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export("set_ODE")]]
SEXP set_ODE(double t, NumericVector state, NumericVector parameters) {
List dn(3);
double tau2 = parameters["tau2"];
double Ae2_4 = parameters["Ae2_4"];
double d2 = parameters["d2"];
double N2 = parameters["N2"];
double n2 = state["n2"];
double m4 = state["m4"];
double ne = state["ne"];
dn[0] = n2*d2 - ne*Ae2_4 - ne/tau2;
dn[1] = ne/tau2 - n2*d2;
dn[2] = -ne*Ae2_4;
return(Rcpp::List::create(dn));
}
/*** R
state <- c(ne = 10, n2 = 0, m4 = 0)
parameters <- c(N2 = 5e17, tau2 = 1e-8, Ae2_4 = 5e3, d2 = 0)
## the following is not yet clear to me !!!
## especially as it is essentially zero
y1 <- c(ne = 0,
n2 = unname(state["n2"] * state["m4"]/parameters["N2"]),
m4 = unname(state["n2"]))
results1 <- deSolve::lsoda(
y = y,
times = 1:2,
func = set_ODE,
parms = parameters
)
## last time step, except "time" column
y2 <- results1[nrow(results1), -1]
results2 <- deSolve::lsoda(
y = y2,
times = 2:10,
func = set_ODE,
parms = parameters
)
## omit 1st time step in results2
results <- rbind(results1, results2[-1, ])
print(results)
*/
У кода есть еще одна потенциальная проблема, поскольку параметры охватывают несколько величин от 1e-8 до 1e17. Это может привести к числовым проблемам, поскольку относительная точность большинства программ, включая R, составляет всего 16 порядков. Может это быть причиной того, что результаты все равны нулю? Здесь может помочь масштабирование модели.