在 R 包中包含 TMB c++ 代码的指南

Guidelines for including TMB c++ code in an R package

我最近发现了 TMB 的神奇之处,并且我正在开发一个包,理想情况下,它会在其中包含 TMB c++ 模板,以用于计算成本相当高的模型。

我假设有可能:

但我在 TMB 文档中找不到关于此的任何明确指南。截至目前,我的替代方案是编写函数,在第一次调用使用未编译 class 的函数时编译 TMB 代码...但我觉得有更好的方法可以做到这一点。

有没有人成功地将 TMB 功能包含在另一个包中,并能为我指明相关文档或示例的方向?

经过更多搜索,我终于在 thread 中找到了我的答案。我想我错过了它,因为它详细说明的决议已移至标题为 development 的维基页面,其中内容专门针对希望为 TMB 的开发做出贡献的用户,而我只是想分发包含 TMB 的代码。

总而言之,线程建议我采取这样的一些更改(myPkg 应该是您的包的名称):

源/

  • 将您的 .cpp 模板放入 mypkg/src。这将在您构建包时由 R 自动编译。

描述

将这些行添加到您的描述文件中,以便 R 拥有编译模型模板所需的所有工具。

Depends: TMB, RcppEigen
LinkingTo: TMB, RcppEigen

R/roxygentags.r

现在我们需要将我们的 TMB 模板添加到命名空间文件中。我们可以通过 roxygen 制作一个像这样的虚拟文件来轻松做到这一点:

#' Roxygen commands
#'
#' @useDynLib myPkg
#'
dummy <- function(){
  return(NULL)
}

虚拟函数只是一个借口,可以在我的源代码中某处添加标签 @useDynLib myPkg,我不会弄乱它。此标记将使用 useDynLib(myPkg) 填充您的 NAMESPACE... 据我了解,这会在为您加载包时加载共享库。

调用包中的函数:

最后,调用MakeADFun时,设置DLL="myPkg"。使用此设置,您可以将 单个 TMB 模型编译到您的包中。这是因为在你的 ./src/ 文件夹中编译的内容会根据你的包名自动重命名,因此你无法创建唯一命名的模型。

编辑:分发多个 DLL 的解决方案

经过更多搜索(与上面引用的线程相同)...我意识到官方 wiki 中描述的解决方案(以及上面详述的)仅与分发单个 dll(即单个 TMB 模型)相关。

如果您想在一个包中分发多个 TMB 模型,则必须使用您自己的 makefile。我已经给出了更详细的描述 in my blog,因此我将在此仅简要描述这些步骤与我之前描述的步骤有何不同。

src/Makefile

您必须定义自己的 Makefile(或 windows 用户的 Makefile.win)并将其放入您的 src/ 目录。这是一个对我有用的例子:

all: template1.so template2.so
    # Comment here preserves the prior tab
template1.so: template1.cpp
    Rscript --vanilla -e "TMB::compile('template1.cpp','-O0 -g')"
template2.so: template2.cpp
    Rscript --vanilla -e "TMB::compile('template2.cpp','-O0 -g')"

clean:
    rm -rf *o 

对于 windows,将 so 替换为 dll,并使用相关的编译器标志(用于调试)。有关用于调试的编译器标志的信息,请参阅 ?TMB::compile

R/roxygentags.r

这与上面略有不同:

#' Roxygen commands
#'
#' This is a dummy function who's purpose is to hold the useDynLib roxygen tag.
#' This tag will populate the namespace with compiled c++ functions upon package install.
#'
#' @useDynLib template1
#' @useDynLib template2
#'
dummy <- function(){
  return(NULL)
}

在包中使用您的模型

最后,上述更改将编译多个唯一命名的 TMB 模板并将它们加载到命名空间中。要在你的包中调用这些模型,这里有一个例子:

obj <- MakeADFun(data = data,
                   parameters = params,
                   DLL="template1", 
                   inner.control = list(maxit = 10000),
                   silent=F)

提示...

当我尝试在 windows 机器上编译它时遇到问题...原来与没有正确清理 src 文件夹有关,我有旧的 linux 编译文件卡在那里。如果您遇到编译问题,值得手动清除 src/ 目录中以前构建的残留文件...或者有人可以提供一些关于编写更好的 make 文件的好建议!

如果您想使用来自 TMB 的附加代码访问 CppAD 库(这非常重要!),那么您可以使用 WITH_LIBTMB 宏变量,就像我在这个 header here. This will allow you to have multiple .cpp files which you can compile separately. Importantly, you only need to compile the code from the TMB header once using a file like this 其中 #include 是 TMB.hpp header 没有定义 WITH_LIBTMB.

这大大减少了编译时间,因为您可以单独编译每个 .cpp,而无需 TMB.hpp 中声明的所有代码。此外,如果你像我在 link.

中那样 undefine and define 一些宏,你也可以将代码与 Rcpp 一起使用

你也可以有一个文件供TMB::MakeADFun使用。它需要一些手动工作,但可以在使用 Rcpp 的同时完成,方法是使用 Rcpp::compileAttributes 并将创建的名为 RcppExports.cpp 的文件更改为命名为 init.cpp,然后将这些附加行包含在CallEntries 数组和 R_init_survTMB 函数:

使用Rstudio注意事项

Rstudio 会在您每次构建时调用 Rcpp::compileAttributes(或类似的东西)。因此,您不能使用它。解决此问题的一种方法是创建自定义构建脚本 similar to the one here。它实际上是在删除 Rcpp::compileAttributes 创建的 RcppExports.cpp 文件后调用 R CMD INSTALL。我也喜欢通过调用 devtools::test() 来 运行 测试,但如果你愿意,可以删除它。