将Fortran和C++与R集成

22

我的任务是将一个R函数重写为C++,以加速while循环。所有的R代码都已经在Rcpp和Armadillo的帮助下进行了重写,除了.Fortran()。我尝试使用Rinside进行加速,但速度非常慢,正如Dirk所指出的那样。(数据需要通过R -> C++ -> R -> Fortran的过程,这很耗时)

既然我不想将Fortran代码重写为C++,反之亦然,因此将C++直接链接到Fortran来加速程序似乎是很自然的选择:R -> C++ -> Fortran。

// [[Rcpp::depends(RcppArmadillo)]]

#include <RcppArmadillo.h>
using namespace Rcpp;

extern "C"{
   List f_(int *n,NumericMatrix a, NumericVector c, double* eps);
}

问题在于我可以将 C++ 与 Fortran 集成,将 R 与 C++ 集成,但我无法使这三个东西一起工作!

我尝试在 Linux 中编译 C++,但它找不到 RcppArmadillo.hnamespace Rcpp

 error: RcppArmadillo.h: No such file or directory
 error: 'Rcpp' is not a namespace-name

当我在R中直接调用sourceCpp("test.cpp")时,控制台会显示:

test.o:test.cpp:(.text+0x20b2): undefined reference to `f_'
collect2: ld returned 1 exit status
Error in sourceCpp("test.cpp") : Error occurred building shared library.

我也尝试将所有这些东西结合在一起打包

RcppArmadillo::RcppArmadillo.package.skeleton("TTTest")

但是在我把.cpp.f文件添加到/src并运行compileAttributes后,我不知道如何处理TTTest包(我认为它无法安装)。

那么,使用Rcpp可以像我想象的那样做吗?或者有必要将Fortran代码转换为C/C++代码吗?

感谢您的帮助。


你的编译器(实际上是预处理器)无法找到文件“RcppArmadillo.h”。确保它与您的“*.cpp”文件在同一个目录中,或者以其他方式可访问(提示:“-I”)。 - набиячлэвэли
是的,应该像 include "RcppArmadillo.h" 这样,但它仍然找不到其他头文件。我怀疑使用 Rcpp 编译文件会花费更多时间,因为最终我会在 R 中调用该函数。 - Dajun Xu
如果这个问题仍然有意义,那么看一下这个答案。在那里调用的函数仍然是一个 C 函数,但最终使用了一个 Fortran 子程序。 - Ralf Stubner
1个回答

29

我建议这样的项目将你的代码整理成一个包。 我创建了一个名为mixedlang的简单示例包,可以在此 GitHub 存储库中找到。 我将在这里描述创建包的过程。

我所采取的步骤如下:

  1. 使用RcppArmadillo::RcppArmadillo.package.skeleton("mixedlang")在 R 中设置包结构(仅使用 RcppArmadillo 而不是 Rcpp,因为 OP 是这样做的 -- 在这个例子中没有任何 Armadillo 特定的内容)
  2. 将下面描述的 C++ 和 Fortran 代码文件添加到src/文件夹中
  3. 在 R 中,运行 Rcpp::compileAttributes("mixedlang/"),然后运行devtools::install("mixedlang/")

代码

我创建了一个简单的 C++ 函数,其唯一目的(本质上)是调用一个 Fortran 函数。该示例函数接受数字向量,将每个元素乘以其索引,然后返回结果。 首先让我们看看 Fortran 代码:

fortranfunction.f90

此函数只需输入两个 double 型数字并将它们相乘,然后返回结果:

REAL*8 FUNCTION MULTIPLY (X, Y) 
REAL*8 X, Y
MULTIPLY = X * Y
RETURN
END

test_function.cpp

现在我们需要从C++代码中调用Fortran代码。在这样做时,我们需要考虑一些事情:

  1. Fortran参数是通过引用而不是值传递的。
  2. 由于MULTIPLY在另一个文件中定义,因此我们需要在我们的C++文件中声明它,以便编译器知道参数和返回类型。

    a. 在为我们的C++文件声明Fortran函数时,我们将放弃函数名称的大小写,并添加下划线,因为Fortran编译器应该默认执行此操作。

    b. 我们必须在extern "C"链接规范内声明函数;C++编译器通常不能使用函数名称作为唯一标识符,因为它允许重载,但是对于调用Fortran函数,我们需要它确切地执行这个任务,而extern "C"链接规范可以实现这一点(例如,参见此 SO 回答)。

#include "RcppArmadillo.h"

// [[Rcpp::depends(RcppArmadillo)]]

// First we'll declare the MULTIPLY Fortran function
// as multiply_ in an extern "C" linkage specification
// making sure to have the arguments passed as pointers.
extern "C" {
    double multiply_(double *x, double *y);
}

// Now our C++ function
// [[Rcpp::export]]
Rcpp::NumericVector test_function(Rcpp::NumericVector x) {
    // Get the size of the vector
    int n = x.size();
    // Create a new vector for our result
    Rcpp::NumericVector result(n);
    for ( int i = 0; i < n; ++i ) {
        // And for each element of the vector,
        // store as doubles the element and the index
        double starting_value = x[i], multiplier = (double)i;
        // Now we can call the Fortran function,
        // being sure to pass the address of the variables
        result[i] = multiply_(&starting_value, &multiplier);
    }
    return result;
}

示例输出

安装完包后,我运行了一个示例

mixedlang::test_function(0:9)
# [1]  0  1  4  9 16 25 36 49 64 81

原帖问题的可能来源

  1. 在最初尝试编译时,他们没有让编译器知道 RcppArmadillo.h 的位置。
  2. 使用 sourceCpp 尝试做这件事情只会给自己找麻烦;它并不真正适用于处理多个文件(例如参见 Dirk Eddelbuettel此答案),而与多种语言打交道时必须使用多个文件。

我不确定当他们尝试将其纳入软件包时发生了什么,这就是我制作这个示例的原因。


2
干得好,需要更多曝光。你愿意在Rcpp Gallery上发布一篇类似的文章吗? - Dirk Eddelbuettel
@DirkEddelbuettel 当然可以。 - duckmayr
3
有趣的是,在https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Package-subdirectories中提到:“在单个软件包中混合使用这些语言可能不可移植(甚至可能根本不可能),我们不支持同时使用C ++和Fortran 9x。”也许需要重新考虑这个说法!或者可能是因为您没有直接将R与Fortran接口? - Ralf Stubner
4
@RalfStubner,你发现了《R扩展手册》上的一个问题!我有点惊讶地看到那里有这样的声明,但是我认为你评论中最后一句话有道理——可能是因为Fortran接口严格来自C++。R只能调用Fortran子程序,而不能调用Fortran函数等,而从C++中调用Fortran通常没有问题,我看不出R调用C++函数会改变这一事实的任何原因。我可能会在答案中更新一些关于这些问题的更多细节。 - duckmayr
@duckmayr 您的回答超出了我的预期。感谢您的解释。 - CKE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接