Подтвердить что ты не робот

Вызов функции R из С++

Я хотел бы, в моем собственном скомпилированном С++-коде, проверить, загружен ли пакет библиотеки в R (если нет, загрузить его), вызвать функцию из этой библиотеки и вернуть результаты в мой код на С++.

Может ли кто-нибудь указать мне в правильном направлении? Кажется, что существует множество информации о R и разных способах вызова R из С++ и наоборот, но я не сталкивался с тем, что я хочу делать.

Спасибо.

4b9b3361

Ответ 1

Дирк, вероятно, прав, что RInside облегчает жизнь. Но для умирающих... Сущность исходит из Написание R Расширений разделов 8.1 и 8.2, а также из примеров, распространенных с R. Материал ниже охватывает построение и оценку вызова; обращение к возвращаемому значению - это другая (и в некотором смысле более легкая) тема.

Настройка

Предположим, что платформа Linux/Mac. Прежде всего, необходимо, чтобы R был скомпилирован, чтобы разрешить связывание либо с общей, либо с статической библиотекой R. Я работаю с SVN-копией источника R в каталоге ~/src/R-devel. Я переключусь на другой каталог, назовите его ~/bin/R-devel, а затем

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

это генерирует ~/bin/R-devel/lib/libR.so; возможно, какое распространение у вас уже есть? Флаг -j запускается параллельно, что значительно ускоряет сборку.

Примеры для вложения находятся в ~/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

В зависимости от ваших амбиций некоторые части раздела 8 расширений Writing R не актуальны, например, обратные вызовы необходимы для реализации GUI поверх R, но не для оценки простых фрагментов кода.

Некоторые детали

Выполнение этого в деталях... SEXP (S-expression) - это структура данных, фундаментальная для представления R базовых типов (целые, логические, языковые вызовы и т.д.). Строка

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

делает символ library и строку "splines", и помещает их в конструкцию языка, состоящую из двух элементов. Это создает неоценимый языковой объект, приблизительно эквивалентный quote(library("splines")) в R. lang2 возвращает SEXP, который был выделен из пула памяти R, и он должен быть PROTECT из коллекции мусора. PROTECT добавляет адрес, на который указывает e, в стек защиты, когда память больше не нуждается в защите, адрес выставляется из стека (с UNPROTECT(1), несколько строк вниз). Строка

    R_tryEval(e, R_GlobalEnv, &errorOccurred);

пытается оценить e в глобальной среде R. errorOccurred устанавливается на не-0, если возникает ошибка. R_tryEval возвращает SEXP, представляющий результат функции, но мы игнорируем его здесь. Поскольку нам больше не нужна память, выделенная для хранения library("splines"), мы говорим R, что она больше не PROTECT'ed.

Следующий фрагмент кода аналогичен, оценивая options(example.ask=FALSE), но построение вызова сложнее. S-выражение, созданное lang2, представляет собой список пар, концептуально с node, левым указателем (CAR) и правым указателем (CDR). Левый указатель e указывает на символ options. Правильный указатель e указывает на другой node в списке пар, левым указателем которого является FALSE (правый указатель R_NilValue, указывающий конец выражения языка). Каждый node списка пар может иметь TAG, смысл которого зависит от роли, которую играет node. Здесь мы добавляем имя аргумента.

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

Следующая строка вычисляет выражение, которое мы построили (options(example.ask=FALSE)), используя NULL, чтобы указать, что мы проигнорируем успех или неудачу оценки функции. Другой способ построения и оценки этого вызова проиллюстрирован в 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-функциях. Вместо этого пишите и управляйте R-кодом в R (например, создавая пакет с функциями, которые выполняют сложную серию манипуляций с R), которые использует ваш код C.

Последний блок кода выше строит и оценивает example("ns"). Rf_tryEval возвращает результат вызова функции, поэтому

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

захватит это для последующей обработки.

Ответ 2

Существует Rcpp, который позволяет вам легко расширять R с помощью кода на С++, а также иметь код С++, обратный к R. Там примеры, включенные в пакет, который показывает это.

Но, может быть, вы действительно хотите сохранить свою С++-программу (т.е. вы владеете main()) и вызывать R? Это можно сделать наиболее легко с помощью RInside, который позволяет вам очень легко вставлять R внутри вашего приложения на С++ - и тест для библиотеки, загрузка при необходимости и вызов функции то очень легко сделать, и (более десятка) включенных примеров показывают вам, как это сделать. И Rcpp все еще помогает вам получать результаты взад и вперед.

Изменить: Как Мартин был достаточно любезен, чтобы показать, что официальный способ я не могу помочь и сопоставить его с одним из примеров доставки с RInside. Это то, что я когда-то писал быстро, чтобы помочь кому-то, кто попросил r-help о том, как загрузить (оптимизацию портфеля) библиотеку и использовать ее. Он отвечает вашим требованиям: загружает библиотеку, обращается к некоторым данным, пропускает вектор весов от С++ до 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, и в пакете есть еще много примеров. Чтобы построить его, вы просто скажете "make rinside_sample2", поскольку поставленный Makefile настроен для поиска R, Rcpp и RInside.