role-model / roleR

R package implementing the RoLE model
https://role-model.github.io/roleR
GNU General Public License v3.0
1 stars 2 forks source link

a possible solution avoiding inline functions but still allowing unit testing of `rcpp` code with `testthat` #30

Open ajrominger opened 1 year ago

ajrominger commented 1 year ago

I was playing around a little with ways to still write modular rcpp code, and have those modules exposed to R so we can use testthat for unit testing. I know we had discussed using the inline specifier to speed up the models but there are two issues with that:

  1. you can't directly make an inline function exposed to R
  2. apparently too many inline functions actually slows down the code again because the machine code gets really big (I'm just reading this on the internet, not understanding it, but trusting it)

So here's an alternative: use classes and methods.

I tried this out with a simple function that makes a matrix of random numbers. The code with a custom class definition for this kind of matrix and a method to populate it with random numbers is actually slightly faster than the flat code where everything is defined in one rcpp function with no special classes.

Here's the concrete example I used:

#include <Rcpp.h>
#include <random>
using namespace Rcpp;

// function to populate a matrix with random numbers
// [[Rcpp::export]]
NumericMatrix initFun(NumericMatrix m, double z) {
    std::mt19937 rng;
    rng.seed(std::random_device{}());
    std::uniform_real_distribution<> dist(0.0, z);

    for (int i = 0; i < m.nrow(); i++) {
        m(i, 0) = dist(rng);
    }

    return m;
}

// class to hold a matrix of random numbers (NOTE: not exported)
class otherThing {
private:
    NumericMatrix value;

public:
    otherThing(NumericMatrix value) : value(value) {}

    // note: the init method uses a function (initFun) defined outside the class
    void initOther(double x) {
        value = initFun(value, x);
    }

    NumericMatrix getValue() const {
        return value;
    }
};

// finally here's a function you can use in R to actually make this matrix of random numbers
// [[Rcpp::export]]
NumericMatrix makeMatMod(NumericMatrix m, double x) {
    otherThing obj(m);
    obj.initOther(x);
    return obj.getValue();
}

Then in R you would use the makeMatMod function like this:

mm <- matrix(-1, nrow = 5000000, ncol = 1)
mmOne <- makeMat(mm, 2.1)

And you could do unit testing of the underlying initFun because it's exposed to R like this:

initFun(matrix(-1, nrow = 2, ncol = 1), 0.5)

We can discuss more! But just wanted to flag that we don't necessarily need to use inline!