web-dev-qa-db-ja.com

C ++からのR関数の呼び出し

自分でコンパイルしたC++コード内で、ライブラリパッケージがRにロードされているかどうかを確認し(ロードされていない場合はロードします)、そのライブラリから関数を呼び出して、結果をC++コードに戻します。

誰かが私を正しい方向に向けることができますか? Rに関する情報が豊富で、C++からRを呼び出したり、CからRを呼び出したりするためのさまざまな方法があるようですが、私がやりたいことを正確に見つけていません。

ありがとう。

35
Erich Peterson

ダークはおそらくRInsideが人生を楽にするのは正しいです。しかし、ダイハードについては...本質は R拡張子の記述 セクション8.1および8.2から、そしてRと共に配布された例から来ています。以下の資料は、呼び出しの作成と評価をカバーしています。戻り値の扱いは別の(そしてある意味で簡単な)トピックです。

セットアップ

Linux/Macプラットフォームを想定しましょう。最初に、Rは、共有または静的Rライブラリへのリンクを許可するようにコンパイルされている必要があります。私はディレクトリ_~/src/R-devel_にあるRのソースのsvnコピーで作業します。他のディレクトリに切り替えて、それを_~/bin/R-devel_と呼び、次に

_~/src/R-devel/configure --enable-R-shlib
make -j
_

これは_~/bin/R-devel/lib/libR.so_を生成します。おそらく、あなたが使用しているどのディストリビューションにもすでにこれがありますか? _-j_フラグはmakeを並行して実行し、ビルドを大幅に高速化します。

埋め込みの例は_~/src/R-devel/tests/Embedding_にあり、_cd ~/bin/R-devel/tests/Embedding && make_で作成できます。明らかに、これらの例のソースコードは非常に有益です。

コード

説明のために、ファイル_embed.cpp_を作成します。まず、Rデータ構造を定義するヘッダーとR埋め込みインターフェースを含めます。これらは_bin/R-devel/include_にあり、主要なドキュメントとして機能します。すべての作業を行う関数のプロトタイプもあります

_#include <Rembedded.h>
#include <Rdefines.h>

static void doSplinesExample();
_

ワークフローは、Rを開始し、作業を行い、Rを終了することです。

_int
main(int argc, char *argv[])
{
    Rf_initEmbeddedR(argc, argv);
    doSplinesExample();
    Rf_endEmbeddedR(0);
    return 0;
}
_

Embeddingの例には、library(splines)を呼び出し、名前付きオプションを設定して、関数example("ns")を実行するものがあります。これを行うルーチンは次のとおりです

_static void
doSplinesExample()
{
    SEXP e, result;
    int errorOccurred;

    // create and evaluate 'library(splines)'
    PROTECT(e = lang2(install("library"), mkString("splines")));
    R_tryEval(e, R_GlobalEnv, &errorOccurred);
    if (errorOccurred) {
        // handle error
    }
    UNPROTECT(1);

    // 'options(FALSE)' ...
    PROTECT(e = lang2(install("options"), ScalarLogical(0)));
    // ... modified to 'options(example.ask=FALSE)' (this is obscure)
    SET_TAG(CDR(e), install("example.ask"));
    R_tryEval(e, R_GlobalEnv, NULL);
    UNPROTECT(1);

    // 'example("ns")'
    PROTECT(e = lang2(install("example"), mkString("ns")));
    R_tryEval(e, R_GlobalEnv, &errorOccurred);
    UNPROTECT(1);
}
_

コンパイルして実行

これで、すべてを組み合わせる準備が整いました。コンパイラは、ヘッダーとライブラリがどこにあるかを知る必要があります

_g++ -I/home/user/bin/R-devel/include -L/home/user/bin/R-devel/lib -lR embed.cpp
_

コンパイルされたアプリケーションは、R_HOMEが正しく設定されているなど、正しい環境で実行する必要があります。これは簡単に調整できます(明らかに、デプロイされたアプリはより広範なアプローチを採用したいと思います)。

_R CMD ./a.out
_

野望によっては、R拡張機能の記述のセクション8の一部は関係ありません。たとえば、Rの上にGUIを実装するためにコールバックが必要ですが、単純なコードチャンクを評価するためではありません。

いくつかの詳細

少し詳しく説明します... SEXP(S式)は、Rの基本型(整数、論理、言語呼び出しなど)の表現の基礎となるデータ構造です。この線

_    PROTECT(e = lang2(install("library"), mkString("splines")));
_

シンボルlibraryと文字列_"splines"_を作成し、2つの要素で構成される言語構成要素に配置します。これは、Rのquote(library("splines"))とほぼ同等の未評価の言語オブジェクトを構築します。_lang2_は、Rのメモリプールから割り当てられたSEXPを返し、ガベージからPROTECTedする必要がありますコレクション。 PROTECTは、eが指すアドレスを保護スタックに追加します。メモリを保護する必要がなくなると、アドレスがスタックからポップされます(UNPROTECT(1)、数行下)。この線

_    R_tryEval(e, R_GlobalEnv, &errorOccurred);
_

rのグローバル環境でeを評価しようとします。エラーが発生した場合、errorOccurredは0以外に設定されます。 _R_tryEval_は、関数の結果を表すSEXPを返しますが、ここでは無視します。 library("splines")を格納するために割り当てられたメモリが不要になったため、Rに保護されていないことを伝えます。

次のコードのチャンクは似ており、options(example.ask=FALSE)を評価しますが、呼び出しの構成はより複雑です。 _lang2_によって作成されたS式はペアリストであり、概念的にはノード、左ポインター(CAR)および右ポインター( CDR)。 eの左ポインタは、シンボルoptionsを指します。 eの右ポインターは、ペアリスト内の別のノードを指しており、その左ポインターはFALSEです(右ポインターは_R_NilValue_であり、言語式の終わりを示します)。ペアリストの各ノードはTAGを持つことができます。その意味は、ノードが果たす役割によって異なります。ここでは引数名を付けます。

_    SET_TAG(CDR(e), install("example.ask"));
_

次の行では、NULLを使用して、作成した式(options(example.ask=FALSE))を評価し、関数の評価の成功または失敗を無視することを示しています。この呼び出しを作成して評価する別の方法を_R-devel/tests/Embedding/RParseEval.c_に示します。

_PROTECT(tmp = mkString("options(example.ask=FALSE)"));
PROTECT(e = R_ParseVector(tmp, 1, &status, R_NilValue));
R_tryEval(VECTOR_ELT(e, 0), R_GlobalEnv, NULL);
UNPROTECT(2);
_

ただし、RコードとCコードが混在していて、R関数で計算された引数を使用できないため、これは一般的には良い戦略のようには思えません。代わりに、Cコードが使用するRでRコードを記述して管理します(たとえば、複雑な一連のR操作を実行する関数を含むパッケージを作成します)。

上記のコードの最後のブロックは、example("ns")を構築して評価します。 _Rf_tryEval_は関数呼び出しの結果を返すため、

_SEXP result;
PROTECT(result = Rf_tryEval(e, R_GlobalEnv, &errorOccurred));
// ...
UNPROTECT(1);
_

後続の処理のためにそれをキャプチャします。

49
Martin Morgan

Rcpp を使用すると、RをC++コードで簡単に拡張でき、そのC++コードでRをコールバックできます。パッケージに含まれている例を示します。

しかし、おそらくあなたが本当に望んでいるのは、C++プログラムを保持して(つまり、main()を所有している)、Rを呼び出すことでしょうか?これは RInside を使用して最も簡単に行うことができます。これにより、C++アプリケーション内にRを非常に簡単に埋め込むことができます---ライブラリのテスト、必要に応じてロードし、関数の呼び出しを非常に簡単に実行できます。 (12を超える)含まれている例は、その方法を示しています。そして Rcpp は、結果をやり取りするのに役立ちます。

編集:マーティンは物事を示すのに十分親切だったので公式の方法私はそれを手伝って対比することはできませんRInsideに同梱されている例の1つ。これは、(ポートフォリオ最適化)ライブラリをロードして使用する方法についてr-helpに質問した人を助けるためにかつて私がかつて書いたものです。それはあなたの要件を満たします:ライブラリをロードし、C++からRに重みベクトルを渡してデータにアクセスし、Rをデプロイして結果を返します。

// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4;  tab-width: 8; -*-
//
// Simple example for the repeated r-devel mails by Abhijit Bera
//
// Copyright (C) 2009         Dirk Eddelbuettel 
// Copyright (C) 2010 - 2011  Dirk Eddelbuettel and Romain Francois

#include <RInside.h>                    // for the embedded R via RInside

int main(int argc, char *argv[]) {

    try {
        RInside R(argc, argv);          // create an embedded R instance 

        std::string txt = "suppressMessages(library(fPortfolio))";
        R.parseEvalQ(txt);              // load library, no return value

        txt = "M <- as.matrix(SWX.RET); print(head(M)); M";
        // assign mat. M to NumericMatrix
        Rcpp::NumericMatrix M = R.parseEval(txt); 

        std::cout << "M has " 
                  << M.nrow() << " rows and " 
                  << M.ncol() << " cols" << std::endl;

        txt = "colnames(M)";        // assign columns names of M to ans and
        // into string vector cnames
        Rcpp::CharacterVector cnames = R.parseEval(txt);   

        for (int i=0; i<M.ncol(); i++) {
            std::cout << "Column " << cnames[i] 
                      << " in row 42 has " << M(42,i) << std::endl;
        }

    } catch(std::exception& ex) {
        std::cerr << "Exception caught: " << ex.what() << std::endl;
    } catch(...) {
        std::cerr << "Unknown exception caught" << std::endl;
    }

    exit(0);
}

この rinside_sample2.cpp、そしてパッケージにはもっとたくさんの例があります。ビルドするには、提供されているMakefileがR、Rcpp、RInsideを見つけるように設定されているので、 'make rinside_sample2'と言うだけです。

35