statnet / network

Classes for Relational Data
Other
15 stars 8 forks source link

using C API from Rcpp package #55

Open slwu89 opened 3 years ago

slwu89 commented 3 years ago

Hello network team!

I am writing a package relying in C++ which is compiled and linked against using Rcpp. I'd like to use the C API of network to interact with "network" class objects within the C++ code, using the extern keyword to make sure the C++ code knows what C function to use, following an example similar to https://github.com/r-pkg-examples/rcpp-and-c. The "network" vignette remarks that the current version implements R's template for registering C routines, so I thought that adding the LinkingTo: network line in my package's DESCRIPTION and including the headers from "network" should work but compiling gives a multiple definition error: multiple definition ofnetRegisterFunctions'`.

My example package is here: https://github.com/slwu89/individual.network

I'd really appreciate any help anyone could provide about how to properly link to network. Thanks!

CarterButts commented 3 years ago

Hi, @slwu89 . It's nice to see someone using this functionality - it was first built in ancient days of yore, and (despite being very fast!) ended up not being used. As such, it has not been actively maintained, and the R folks seem to have been making aggressive changes to how this stuff works that may have broken it. The upshot of this is that I will need to do some digging and look into it. I've not yet started playing with linking to the C backend from Rcpp (being only a recent and somewhat reluctant C++ user - Rcpp inlining was the "killer app" that started seducing me to the dark side), but that is something that would be very useful to support, so this seems like the time to figure it out. :-)

knapply commented 3 years ago

I modified the #includes to limit what gets pulled into /src/net.h: https://github.com/knapply/individual.network

Happy to PR, but you can get the gist by looking at the diff here: https://github.com/slwu89/individual.network/compare/main...knapply:main

# remotes::install_github("knapply/individual.network")

m <- matrix(rbinom(25,1,.4),5,5)
diag(m) <- 0
g <- network::network(m, directed=FALSE)
g
#>  Network attributes:
#>   vertices = 5 
#>   directed = FALSE 
#>   hyper = FALSE 
#>   loops = FALSE 
#>   multiple = FALSE 
#>   bipartite = FALSE 
#>   total edges= 3 
#>     missing edges= 0 
#>     non-missing edges= 3 
#> 
#>  Vertex attribute names: 
#>     vertex.names 
#> 
#> No edge attributes

individual.network::rnbernexp_CPP(g, 0.5, 0.5, 0.5) # I just plugged in some args to confirm it runs...
#>  Network attributes:
#>   vertices = 5 
#>   directed = FALSE 
#>   hyper = FALSE 
#>   loops = FALSE 
#>   multiple = FALSE 
#>   bipartite = FALSE 
#>   FirstOnsetTime = 0.4029905 
#>   LastTerminationTime = 1.456397 
#>   total edges= 7 
#>     missing edges= 0 
#>     non-missing edges= 7 
#> 
#>  Vertex attribute names: 
#>     FirstOnsetTime LastTerminationTime vertex.names 
#> 
#>  Edge attribute names: 
#>     OnsetTime TerminationTime
slwu89 commented 3 years ago

Hello @CarterButts and @knapply!

Thanks so much for the quick help, I really appreciate it. Fixing where the network headers should be included according to @knapply's help, I wrote a working minimal example package with an explanation of the steps required to use the C API from Rcpp code on a fork of the repo here:

https://github.com/slwu89/network/tree/example_rcpp_pkg/inst/examples/networkRcppExample

If you think it's useful I'd be happy to submit a PR.

knapply commented 3 years ago

Just my 2 cents: this seems more appropriate as a standalone repo -- maybe to point to from a vignette?

Another option to consider would be something analogous to Rcpp::Rcpp.package.skeleton()/RcppEigen::RcppEigen.package.skeleton()/RcppArmadillo::RcppArmadillo.package.skeleton().

image

I'm a huge fan of Rcpp (and coauthor {RcppSimdJson} with Dirk) -- @CarterButts, if you haven't considered it already, it'd be extremely appealing as a downstream {network} user to be able to tap into a network object class at the C++ level. This is super contrived, but this sort of behavior would be wildly convenient...

RcppNetwork.cpp:

#include <Rcpp.h>

// hypothetical RcppNetwork.hpp
namespace RcppNetwork {

class Network {
  Rcpp::List g;
  bool directed;

public:
  Network(const Rcpp::List g_) : g(g_) {
    const Rcpp::List gal = this->g["gal"];
    const Rcpp::LogicalVector directed = gal["directed"];
    this->directed = directed[0];
  };

  bool is_directed() const {
    return this->directed;
  }
};

} // namespace RcppNetwork
// hypothetical RcppNetwork.hpp

// [[Rcpp::export]]
bool is_directed(const Rcpp::List x) {
  const auto g = RcppNetwork::Network(x);
  return g.is_directed();
}

/*** R
m <- matrix(rbinom(25,1,.4),5,5)
g <- network::network(m, directed=FALSE)

is_directed(g)
*/
Rcpp::sourceCpp('~/r-projects/RcppNetwork.cpp')
##> m <- matrix(rbinom(25,1,.4),5,5)
##> g <- network::network(m, directed=FALSE)

##> is_directed(g)
##> [1] FALSE
slwu89 commented 3 years ago

Sure @knapply, I have no strong opinions on where, if anywhere it should live. I was looking at what RcppProgress did for their "sample" packages https://github.com/kforner/rcpp_progress/tree/master/inst/examples , but it looks like the statenet org has room for sample packages.

Is your suggestion to use Rcpp Attributes to export some header from network that Rcpp users can easily include? I agree that would be wildly convenient, but also looks like a lot of refactoring work on yours/ @CarterButts end? I'd be happy to help in a few months, but for now the method in the networkRcppExample package has helped me get off the ground for using network in my project.

knapply commented 3 years ago

I'm just a guest here. It's totally up to @CarterButts and the Statnet team.

slwu89 commented 3 years ago

Ah understood. Thanks for helping out another guest!