Skip to content

Commit

Permalink
Updates through packaging section
Browse files Browse the repository at this point in the history
  • Loading branch information
richfitz committed Nov 7, 2024
1 parent 30dbbee commit 2e1b3e5
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 4 deletions.
2 changes: 1 addition & 1 deletion common.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ r_output <- function(x) {
}

plain_output <- function(x) {
lang_output(x, "plain")
lang_output(x, "")
}
4 changes: 2 additions & 2 deletions interpolation.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ logistic_t <- odin({
K <- parameter()
r0 <- parameter()
p <- parameter(0.2)
r <- r0 * (1 - p) + r0 * p * (1 + sin(time * 2 * 3.141))
r <- r0 * (1 - p) + r0 * p * (1 + sin(time * 2 * pi))
})
```

Expand Down Expand Up @@ -143,7 +143,7 @@ matplot(t, t(y), type = "l", lty = 1, col = "black",
xlab = "Time", ylab = "Variable")
```

With `dust_system_simulate()` the solver will stop at every time, so we'll never jump over the solution. But if you were running this where you were just advancing the solution through time you might do so. We get our expected value of (approximately) 1 at time 25 whenere `r1` is 15:
With `dust_system_simulate()` the solver will stop at every time, so we'll never jump over the solution. But if you were running this where you were just advancing the solution through time you might do so. We get our expected value of (approximately) 1 at time 25 whenever `r1` is 15:

```{r}
sys <- dust_system_create(wave, list(r1 = 15))
Expand Down
2 changes: 1 addition & 1 deletion odin.qmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Getting started with odin
# Getting started with odin {#sec-getting-started}

```{r}
#| include: false
Expand Down
149 changes: 149 additions & 0 deletions packaging.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ So far, we have compiled odin code as we have needed it, using the `odin()` func
* Packages are much easier for other people to use than standalone R scripts
* There is nice tooling available to packages for configuring dependencies, tests and other automatic checks (see below).

```{r}
library(odin2)
```

## A basic package skeleton

Many tools exist to create packages; here we will use [`usethis`](https://usethis.r-lib.org/), but there's nothing magic here - you could create these files by hand if you prefer (see [Writing R Extensions](https://cran.r-project.org/doc/manuals/r-release/R-exts.html) for the official guide on R packages if this is the approach you prefer).
Expand All @@ -39,9 +43,154 @@ usethis::create_package(
fields = list(Package = "pkg",
Title = "My Odin Model",
Description = ""))
usethis::proj_set(path)
```

Our package now contains:

```{r}
fs::dir_tree(path)
```

`usethis` has created some skeleton files for us. The `DESCRIPTION` contains:

```{r}
#| echo: false
#| results: asis
plain_output(readLines(file.path(path, "DESCRIPTION")))
```

and the `NAMESPACE` file contains

```{r}
#| echo: false
#| results: asis
plain_output(readLines(file.path(path, "NAMESPACE")))
```

## Adding odin code

The odin code needs to go within this package in the `inst/odin` directory. Code saved as `inst/odin/myname.R` will create a generator called `myname`. Here, we create a file `inst/odin/sir.R` containing the SIR model from @sec-getting-started:

```{r}
#| echo: false
#| results: asis
dir.create(file.path(path, "inst/odin"), FALSE, TRUE)
writeLines(
c("deriv(S) <- -beta * S * I / N",
"deriv(I) <- beta * S * I / N - gamma * I",
"deriv(R) <- gamma * I",
"",
"initial(S) <- N - I0",
"initial(I) <- I0",
"initial(R) <- 0",
"",
"N <- parameter(1000)",
"I0 <- parameter(10)",
"beta <- parameter(0.2)",
"gamma <- parameter(0.1)"),
file.path(path, "inst/odin/sir.R"))
r_output(readLines(file.path(path, "inst/odin/sir.R")))
```

We also need to make some changes to our package:

* We need to include `dust2` as an `Imports` dependency
* We need to include `dust2`, `monty` and `cpp11` as `LinkingTo` dependencies
* We need to arrange our package so it loads the shared library that we build

If you run `odin_package()` before setting this up, it will error and indicate where the problem lies:

```{r}
#| error: true
odin_package(path)
```

```{r}
usethis::use_package("dust2", "Imports")
usethis::use_package("dust2", "LinkingTo")
usethis::use_package("monty", "LinkingTo")
```

Setting up to use `cpp11` is a bit more involved because of the changes that we need to make to ensure that everything links together correctly; for details see the [packaging section of the `cpp11` "Getting started" vignette](https://cpp11.r-lib.org/articles/cpp11.html#package)

```{r}
usethis::use_package_doc()
usethis::use_cpp11()
devtools::document(path)
fs::file_delete(file.path(path, "src/code.cpp"))
```

We can now generate our odin code:

```{r}
odin_package(path)
```

The package now contains more files:

```{r}
fs::dir_tree(path)
```

Almost every new file here should not be edited directly (and all contain a line at the start to that effect).

Our `DESCRIPTION` now contains

```{r}
#| echo: false
#| results: asis
plain_output(readLines(file.path(path, "DESCRIPTION")))
```

and `NAMESPACE` contains

```{r}
#| echo: false
#| results: asis
plain_output(readLines(file.path(path, "NAMESPACE")))
```

In `R/`:

* `cpp11.R` is the glue code generated by `cpp11`
* `dust.R` is glue code generated by `dust2`
* `pkg-package.R` was generated by `usethis::use_package_doc()` and holds the special `roxygen2` comments that caused `devtools::document()` to write our `NAMESPACE` file (you can edit this file!)

```{r}
#| echo: false
#| results: asis
r_output(readLines(file.path(path, "R/pkg-package.R")))
```

In `inst/dust`, `sir.cpp` contains the dust interface for our model (see the ["Writing dust2 systems" vignette](https://mrc-ide.github.io/dust2/articles/writing.html) if you are curious)

In `man/`, `pkg-package.Rd` contains help files generated by `roxygen2`

In `src/`

* `Makevars` contains code to allow OpenMP to work to parallelise the system
* `cpp11.cpp` is glue code generated by `cpp11`
* `sir.cpp` is the full system code generated by `dust2`
* `code.cpp` was added by `cpp11` and can be removed
* The `.o` and `.so` (or `.dll` on Windows) files are generated by the compiler

## Development of the package

Once your odin code is in a package, you will want to iterate over it. Previously you might have had scripts that you used `source()` on to load into the session. Now your workflow looks like:

1. Edit the odin code in `inst/odin`
2. Run `odin_package()`
3. Load the package with `pkgload::load_all()`

If you edit code in `R/` you don't need to run step 2, and running step 3 is enough.

```{r}
pkgload::load_all(path)
```

If you are using RStudio then `Ctrl-Shift-l` will load the package for you.

## Next steps

Once you have a model in a package, then you are within the realms of normal R package development, and nothing here is specific to odin. However, if you are not familiar with package development we hope these tips will be useful.
Expand Down

0 comments on commit 2e1b3e5

Please sign in to comment.