---
title: "04 Advanced `SpaDES` use"
author:
  - "Alex M. Chubaty"
  - "Eliot J. B. McIntire"
date: "`r strftime(Sys.Date(), '%B %d %Y')`"
output:
  rmarkdown::html_vignette:
    number_sections: yes
    self_contained: yes
    toc: yes
vignette: >
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteIndexEntry{04 Advanced `SpaDES` use}
  %\VignetteKeyword{discrete event simulation, spatial simulation models}
  %\VignetteEncoding{UTF-8}
bibliography: bibliography.bib
---

# Advanced `SpaDES` use

This vignette covers tools that become useful once you are building or
maintaining a substantial `SpaDES` workflow: monitoring memory use across
events, and statically checking that a module's code agrees with its
declared metadata.

## Memory monitoring

`profvis::profvis` is great for digging into R internals, but it doesn't fit
the discrete-event pattern very well. Often the question is simpler: which
event uses the most memory, and how much? That number sets how many parallel
runs you can fit on a machine.

`SpaDES.core` ships a small background sampler. Turn it on by setting the
option `spades.memoryUseInterval` to a sampling interval in seconds (e.g.,
`0.2`). If `future` and `future.callr` are installed, each `spades()` call
will then:

1. spawn a `future` (parallel) R session that runs `system('ps')` once per
   interval, keeping only the row for the main R process;
2. write that to a text file;
3. when `spades()` returns, read the file into `sim@.xData$.memoryUse$obj`
   and delete the file;
4. [memoryUse()] joins that table to [completed()] by timestamp, so each
   event is annotated with its memory use.

```{r memoryUse, eval=FALSE, echo=TRUE}
if (requireNamespace("future", quietly = TRUE) &&
    requireNamespace("future.callr", quietly = TRUE)) {
  options(spades.memoryUseInterval = 0.5)

  # sim <- simInit(...)
  # sim <- spades(sim)

  memoryUse(sim, max = TRUE)  # peak memory by event type (summarised across times)
  memoryUse(sim, max = FALSE) # peak memory for every individual event
}
```

[memoryUseThisSession()] returns the memory currently used by the running R
session; useful as a quick sanity check between events.

## Static code checking

`simInit()` parses your module's source and statically checks that the code
agrees with the metadata: every read of `sim$x` should have `x` listed in
`expectsInput()` or `createsOutput()`, every parameter access (`Par$y`,
`P(sim)$y`, `params(sim)$mod$y`) should match a `defineParameter()` entry,
every `doEvent.*` function should return `sim`, and so on. Mismatches are
reported as a structured table with file/line/column locations.

The checks are controlled by two options:

```{r codecheck-options, eval=FALSE}
options(spades.moduleCodeChecks = TRUE)    # turn checking on/off
options(spades.codeCheckEngine = "v2")     # "v1" (legacy) or "v2" (new, structured)
```

You can also run the checks on a module on disk, without going through
`simInit()`. This is handy while authoring or refactoring a module:

```{r codecheck-standalone, eval=FALSE}
# returns a data.frame of findings; also prints a grouped report
findings <- codeCheckModule("path/to/myModule")

# subset by severity or rule id
findings[findings$severity == "warning", ]
findings[findings$id == "param_used_undeclared", ]
```

A finding has columns `id`, `severity`, `module`, `where`, `name`, `fn`,
`file`, `line`, `col`, `message`, `suggestion`. The suggestion is a
plain-language fix (e.g., "add `defineParameter('coverThresh', ...)` to
parameters"). When `simInit()` runs the checks, the same data.frame is
stashed at `sim@.xData$.codeCheck[[moduleName]]`.

Typical findings the checker flags:

| id | what it catches |
|---|---|
| `out_declared_unused` | output declared in metadata, never assigned in code |
| `out_used_undeclared` | `sim$x <- ...` for `x` not in `createsOutput()` |
| `in_declared_unused` | input declared, never read |
| `in_used_undeclared` | `sim$x` read for `x` not in `expectsInput()` |
| `param_declared_unused` | `defineParameter("x", ...)` but no `Par$x` in the code |
| `param_used_undeclared` | `Par$x` / `P(sim)$x` for `x` not declared |
| `unresolved_accessor` | `sim[[expr]]` or `get(var, envir=envir(sim))` — can't be checked statically |
| `must_return_sim` | `doEvent.*` not ending with `return(invisible(sim))` |
| `must_assign_to_sim` | `scheduleEvent(...)` or `saveFiles(...)` not assigned back to `sim` |
| `module_named_object` | `sim$<moduleName> <- ...` (forbidden) |
| `conflicting_fn_unqualified` | bare `levels`/`scale`/`which.max` (ambiguous with `raster::`) |

See [codeCheckModule()] for details.

# References
