1

I have a simulation model which is written as a C++ class. I have written a simple Rcpp wrapper to allow it to be run from R. I am wondering about the best way to get and set the model properties (doubles, vectors of doubles, and vectors of vectors of doubles) from R. I am using three std::unordered_map as a way of allowing the user to access the model properties:

class molly_class {

  ...

  public:

  // unordered_map gives user efficient access to variables by name
  std::unordered_map< std::string , double > variable;
  std::unordered_map< std::string , std::vector< double >* > vector; // pointers to vectors
  std::unordered_map< std::string , std::vector< std::vector< double > >* > array; // pointers to arrays

  ...

}

In my Rcpp wrapper I have provided methods to access the "variables" (scalars), "vectors" and "arrays" from R. It works, but I unsure whether this is the right way to go. I want to avoid unnecessary copying of information, and maybe there are more efficient/safer ways of passing the data back and forth? I looked in the Rcpp documentation about exposing C++ classes and RCCP_MODULE but I don't really understand it. Thanks in advance. The Rcpp wrapper contains the following:

molly_class molly;

// return molly scalar variables and their values as a named vector
// [[Rcpp::export]]
Rcpp::NumericVector get_molly_variables(){
    Rcpp::NumericVector var_vector;
    molly.pull_variables_from_model();
    var_vector = molly.variable; // coerces unordered_map to named vector
    return var_vector;
}

// set values of molly variables from the supplied named vector
// [[Rcpp::export]]
void set_molly_variables(Rcpp::NumericVector var_vector){
    Rcpp::CharacterVector names = var_vector.names(); // types are a bit tricky
    std::string name_i;
    molly.pull_variables_from_model();
    for (int i=0; i<var_vector.size(); ++i){
        name_i = Rcpp::as<std::string>(names[i]); // types are a bit tricky
        if (molly.variable.find(name_i) != molly.variable.end()){
            molly.variable[name_i] = var_vector[i];
        } else {
            Rcpp::Rcout << "molly error : unknown variable " << name_i << std::endl;
        }
    }
    molly.push_variables_to_model();
}

// return names of molly vectors
// [[Rcpp::export]]
Rcpp::StringVector get_molly_vectors(){
    Rcpp::StringVector key_vector;
    // https://stackoverflow.com/questions/8483985/obtaining-list-of-keys-and-values-from-unordered-map
    for (auto mvi : molly.vector){
        key_vector.push_back(mvi.first);
    }
    return key_vector;
}

// return a molly vector 
// [[Rcpp::export]]
Rcpp::NumericVector get_molly_vector(Rcpp::String vec_name){
    Rcpp::NumericVector vec_values;
    vec_values = *molly.vector[vec_name];
    return vec_values;
}

// set a molly vector using the supplied vector
// [[Rcpp::export]]
void set_molly_vector(Rcpp::String vec_name, Rcpp::NumericVector vec_values){
    int ncols = (*molly.vector[vec_name]).size();
    if (vec_values.size() == ncols){
        for (int col=0; col<ncols; col++){
            (*molly.vector[vec_name]).at(col) = vec_values.at(col);
        }
    } else {
        Rcpp::Rcout << "molly error : supplied vector does not match length of " << vec_name << std::endl;
    }
}

// return names of molly arrays
// [[Rcpp::export]]
Rcpp::StringVector get_molly_arrays(){
    Rcpp::StringVector key_vector;
    // https://stackoverflow.com/questions/8483985/obtaining-list-of-keys-and-values-from-unordered-map
    // key_vector.reserve(molly.vector.size());
    for (auto mvi : molly.array){
        key_vector.push_back(mvi.first);
    }
    return key_vector;
}

// return a molly array as a matrix
// [[Rcpp::export]]
Rcpp::NumericMatrix get_molly_array(Rcpp::String arr_name){
    int nrows = (*molly.array[arr_name]).size();
    int ncols = (*molly.array[arr_name]).at(0).size();
    Rcpp::NumericMatrix arr_values(nrows, ncols);
    for (int row=0; row<nrows; row++){
        for (int col=0; col<ncols; col++){
            arr_values(row, col) =  (*molly.array[arr_name]).at(row).at(col);
        }
    }
    return arr_values;
}

// set a molly array using the supplied matrix
// [[Rcpp::export]]
void set_molly_array(Rcpp::String arr_name, Rcpp::NumericMatrix arr_values){
    int nrows = (*molly.array[arr_name]).size();
    int ncols = (*molly.array[arr_name]).at(0).size();
    if (arr_values.nrow() == nrows && arr_values.ncol() == ncols){
        for (int row=0; row<nrows; row++){
            for (int col=0; col<ncols; col++){
                (*molly.array[arr_name]).at(row).at(col) = arr_values(row, col);
            }
        }
    } else {
        Rcpp::Rcout << "molly error : supplied matrix does not match dimensions of " << arr_name << std::endl;
    }

Note that pull_variables_from_model() populates the unordered_map variable and push_variables_to_model() uses the unordered_map variable to set the internal model variables, which are private members of molly_class. The unordered_map variable is only used as an interface to allow users to access the internal model variables by name.

Simon Woodward
  • 1,946
  • 1
  • 16
  • 24
  • 2
    The interface between R and C(++) only allows for basic types (scalar or vectors) as well as lists containing these so what you do it probably the best you can do. The devil is, as always, in the detail but there are 1500+ CRAN packages using Rcpp to give you ideas. – Dirk Eddelbuettel Jan 29 '19 at 13:46
  • Thank you @Dirk, it's good to know I'm not completely misguided!. – Simon Woodward Jan 29 '19 at 18:58

0 Answers0