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.
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:
Package
). We’re using
example
, and names with a dot may not work as expectedcpp11
and
dust
in LinkingTo
, which allows package
compilation to find their respective header filesuseDynLib
call to your package in the
NAMESPACE
fileOur 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/Rtmp2OzD9L/filebc1d6cf3b4'
#> ℹ 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.15) - 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.15) - 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()
.