Packaging dust systems

You should not use dust_compile() within a package, because that would cause the model to compile each time you use it, rather than when the package builds. It may also cause issues when trying to use the model in parallel (e.g., with the parallel package), and will require all users of your code to have a full C++ toolchain. Instead you should use dust_package() which will generate appropriate code for you.

library(dust2)

To use dust in a package, put your dust source files in inst/dust and run dust_package() on the package’s root directory. You can have several dust systems within a single package.

A skeleton package might contain:

#> .
#> ├── DESCRIPTION
#> ├── NAMESPACE
#> └── inst
#>     └── dust
#>         └── walk.cpp

This is the normal R package skeleton, though missing R/ and src/ directories (for now). The DESCRIPTION file contains

Package: example
Title: Example Dust in a Package
Version: 0.0.1
Imports: dust2
LinkingTo: cpp11, dust2, monty
Authors@R: c(person('A', 'Person', role = c('aut', 'cre')
                     email = '[email protected]'))
License: CC0

The important things here are:

Our NAMESPACE file contains:

useDynLib('example', .registration = TRUE)

The files in inst/dust are the same files as seen above, with walk.cpp containing

#include <dust2/common.hpp>

// [[dust2::class(walk)]]
// [[dust2::time_type(discrete)]]
// [[dust2::parameter(sd)]]
class walk {
public:
  walk() = delete;

  using real_type = double;

  struct shared_state {
    real_type sd;
  };

  struct internal_state {};

  using rng_state_type = monty::random::generator<real_type>;

  static dust2::packing packing_state(const shared_state& shared) {
    return dust2::packing{{"x", {}}};
  }

  static shared_state build_shared(cpp11::list pars) {
    const auto sd = dust2::r::read_real(pars, "sd");
    return shared_state{sd};
  }

  static void update_shared(cpp11::list pars, shared_state& shared) {
    shared.sd = dust2::r::read_real(pars, "sd", shared.sd);
  }

  static void initial(real_type time,
                      const shared_state& shared,
                      internal_state& internal,
                      rng_state_type& rng_state,
                      real_type * state_next) {
    state_next[0] = 0;
  }

  static void update(real_type time,
                     real_type dt,
                     const real_type * state,
                     const shared_state& shared,
                     internal_state& internal,
                     rng_state_type& rng_state,
                     real_type * state_next) {
    state_next[0] = monty::random::normal(rng_state, state[0], shared.sd * dt);
  }
};

There can be as many of these files as you want within the directory inst/dust.

To prepare the package, run dust_package():

dust_package(path)
#> ℹ Working in package 'example' at '/tmp/RtmpklL4rQ/filebc8f00c8b4'
#> ℹ Found 1 system
#> ✔ Wrote 'src/walk.cpp'
#> ✔ Wrote 'R/dust.R'
#> ✔ Wrote 'src/Makevars'
#> ℹ 12 functions decorated with [[cpp11::register]]
#> ✔ generated file 'cpp11.R'
#> ✔ generated file 'cpp11.cpp'

The directory structure now has more files:

#> .
#> ├── DESCRIPTION
#> ├── NAMESPACE
#> ├── R
#> │   ├── cpp11.R
#> │   └── dust.R
#> ├── inst
#> │   └── dust
#> │       └── walk.cpp
#> └── src
#>     ├── Makevars
#>     ├── cpp11.cpp
#>     └── walk.cpp

The file src/walk.cpp is generated by dust and should not be edited. They include your model, but also a bit of helper code:

// Generated by dust2 (version 0.3.9) - do not edit

#include <dust2/common.hpp>

// [[dust2::class(walk)]]
// [[dust2::time_type(discrete)]]
// [[dust2::parameter(sd)]]
class walk {
public:
  walk() = delete;

  using real_type = double;

  struct shared_state {
    real_type sd;
  };

  struct internal_state {};

  using rng_state_type = monty::random::generator<real_type>;

  static dust2::packing packing_state(const shared_state& shared) {
    return dust2::packing{{"x", {}}};
  }

  static shared_state build_shared(cpp11::list pars) {
    const auto sd = dust2::r::read_real(pars, "sd");
    return shared_state{sd};
  }

  static void update_shared(cpp11::list pars, shared_state& shared) {
    shared.sd = dust2::r::read_real(pars, "sd", shared.sd);
  }

  static void initial(real_type time,
                      const shared_state& shared,
                      internal_state& internal,
                      rng_state_type& rng_state,
                      real_type * state_next) {
    state_next[0] = 0;
  }

  static void update(real_type time,
                     real_type dt,
                     const real_type * state,
                     const shared_state& shared,
                     internal_state& internal,
                     rng_state_type& rng_state,
                     real_type * state_next) {
    state_next[0] = monty::random::normal(rng_state, state[0], shared.sd * dt);
  }
};

#include <cpp11.hpp>
#include <dust2/r/discrete/system.hpp>

[[cpp11::register]]
SEXP dust2_system_walk_alloc(cpp11::list r_pars, cpp11::sexp r_time, cpp11::list r_time_control, cpp11::sexp r_n_particles, cpp11::sexp r_n_groups, cpp11::sexp r_seed, cpp11::sexp r_deterministic, cpp11::sexp r_n_threads) {
  return dust2::r::dust2_discrete_alloc<walk>(r_pars, r_time, r_time_control, r_n_particles, r_n_groups, r_seed, r_deterministic, r_n_threads);
}
[[cpp11::register]]
SEXP dust2_system_walk_run_to_time(cpp11::sexp ptr, cpp11::sexp r_time) {
  return dust2::r::dust2_system_run_to_time<dust2::dust_discrete<walk>>(ptr, r_time);
}

[[cpp11::register]]
SEXP dust2_system_walk_state(cpp11::sexp ptr, cpp11::sexp r_index_state, cpp11::sexp r_index_particle, cpp11::sexp r_index_group, bool preserve_particle_dimension, bool preserve_group_dimension) {
  return dust2::r::dust2_system_state<dust2::dust_discrete<walk>>(ptr, r_index_state, r_index_particle, r_index_group, preserve_particle_dimension, preserve_group_dimension);
}

[[cpp11::register]]
SEXP dust2_system_walk_time(cpp11::sexp ptr) {
  return dust2::r::dust2_system_time<dust2::dust_discrete<walk>>(ptr);
}

[[cpp11::register]]
SEXP dust2_system_walk_set_state_initial(cpp11::sexp ptr) {
  return dust2::r::dust2_system_set_state_initial<dust2::dust_discrete<walk>>(ptr);
}

[[cpp11::register]]
SEXP dust2_system_walk_set_state(cpp11::sexp ptr, cpp11::list r_state) {
  return dust2::r::dust2_system_set_state<dust2::dust_discrete<walk>>(ptr, r_state);
}

[[cpp11::register]]
SEXP dust2_system_walk_reorder(cpp11::sexp ptr, cpp11::integers r_index) {
  return dust2::r::dust2_system_reorder<dust2::dust_discrete<walk>>(ptr, r_index);
}

[[cpp11::register]]
SEXP dust2_system_walk_rng_state(cpp11::sexp ptr) {
  return dust2::r::dust2_system_rng_state<dust2::dust_discrete<walk>>(ptr);
}

[[cpp11::register]]
SEXP dust2_system_walk_set_rng_state(cpp11::sexp ptr, cpp11::sexp r_rng_state) {
  return dust2::r::dust2_system_set_rng_state<dust2::dust_discrete<walk>>(ptr, r_rng_state);
}

[[cpp11::register]]
SEXP dust2_system_walk_set_time(cpp11::sexp ptr, cpp11::sexp r_time) {
  return dust2::r::dust2_system_set_time<dust2::dust_discrete<walk>>(ptr, r_time);
}

[[cpp11::register]]
SEXP dust2_system_walk_update_pars(cpp11::sexp ptr, cpp11::list pars) {
  return dust2::r::dust2_system_update_pars<dust2::dust_discrete<walk>>(ptr, pars);
}

[[cpp11::register]]
SEXP dust2_system_walk_simulate(cpp11::sexp ptr, cpp11::sexp r_times, cpp11::sexp r_index_state, bool preserve_particle_dimension, bool preserve_group_dimension) {
  return dust2::r::dust2_system_simulate<dust2::dust_discrete<walk>>(ptr, r_times, r_index_state, preserve_particle_dimension, preserve_group_dimension);
}

The file R/dust.R contains the R interface generated by dust with the constructor objects (all models’ constructors will be collected into this file, which also should not be edited).

## Generated by dust2 (version 0.3.9) - do not edit
walk <- structure(
  function() get("walk"),
  class = "dust_system_generator",
  name = "walk",
  package = "example",
  path = NULL,
  parameters = data.frame(
    name = "sd",
    type = "real_type"),
  properties = list(
    time_type = "discrete",
    has_compare = FALSE,
    has_adjoint = FALSE),
  default_dt = 1)

Finally, R/cpp11.R and src/cpp11.cpp are files created by cpp11 that should not be edited.

Your package can include as much R code as you want, and can be developed like any other R package. But any time you change the code in inst/dust you should rerun dust_package().