--- title: "Introduction to simmer" author: "Bart Smeets, IƱaki Ucar" description: > Learn how to get started with the basics of simmer. date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: yes vignette: > %\VignetteIndexEntry{01. Introduction to simmer} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, cache = FALSE, include=FALSE} knitr::opts_chunk$set(collapse = T, comment = "#>", fig.width = 6, fig.height = 4, fig.align = "center") ``` ## Basic usage First, load the package and instantiate a new simulation environment. ```{r, message=FALSE} library(simmer) set.seed(42) env <- simmer("SuperDuperSim") env ``` Set-up a simple trajectory. Let's say we want to simulate an ambulatory consultation where a patient is first seen by a nurse for an intake, next by a doctor for the consultation and finally by administrative staff to schedule a follow-up appointment. ```{r} patient <- trajectory("patients' path") %>% ## add an intake activity seize("nurse", 1) %>% timeout(function() rnorm(1, 15)) %>% release("nurse", 1) %>% ## add a consultation activity seize("doctor", 1) %>% timeout(function() rnorm(1, 20)) %>% release("doctor", 1) %>% ## add a planning activity seize("administration", 1) %>% timeout(function() rnorm(1, 5)) %>% release("administration", 1) ``` In this case, the argument of the `timeout` activity is a function, which is evaluated dynamically to produce a stochastic waiting time, but it could be a constant too. Apart from that, this function may be as complex as you need and may do whatever you want: interact with entities in your simulation model, get resources' status, make decisions according to the latter... Once the trajectory is known, you may attach arrivals to it and define the resources needed. In the example below, three types of resources are added: the *nurse* and *administration* resources, each one with a capacity of 1, and the *doctor* resource, with a capacity of 2. The last method adds a generator of arrivals (patients) following the trajectory `patient`. The time between patients is about 10 minutes (a Gaussian of `mean=10` and `sd=2`). (Note: returning a negative interarrival time at some point would stop the generator). ```{r} env %>% add_resource("nurse", 1) %>% add_resource("doctor", 2) %>% add_resource("administration", 1) %>% add_generator("patient", patient, function() rnorm(1, 10, 2)) ``` The simulation is now ready for a test run; just let it *simmer* for a bit. Below, we specify that we want to limit the runtime to 80 time units using the `until` argument. After that, we verify the current simulation time (`now`) and when will be the next 3 events (`peek`). ```{r, message=FALSE} env %>% run(80) %>% now() env %>% peek(3) ``` It is possible to run the simulation step by step, and such a method is chainable too. ```{r, message=FALSE} env %>% stepn() %>% # 1 step print() %>% stepn(3) # 3 steps env %>% peek(Inf, verbose=TRUE) ``` Also, it is possible to resume the automatic execution simply by specifying a longer runtime. Below, we continue the execution until 120 time units. ```{r, message=FALSE} env %>% run(120) %>% now() ``` You can also reset the simulation, flush all results, resources and generators, and restart from the beginning. ```{r, message=FALSE} env %>% reset() %>% run(80) %>% now() ``` ## Replication It is very easy to replicate a simulation multiple times using standard R functions. ```{r} envs <- lapply(1:100, function(i) { simmer("SuperDuperSim") %>% add_resource("nurse", 1) %>% add_resource("doctor", 2) %>% add_resource("administration", 1) %>% add_generator("patient", patient, function() rnorm(1, 10, 2)) %>% run(80) }) ``` The advantage of the latter approach is that, if the individual replicas are heavy, it is straightforward to parallelise their execution (for instance, in the next example we use the function `mclapply` from the [parallel](https://stat.ethz.ch/R-manual/R-devel/library/parallel/doc/parallel.pdf)) package. However, the external pointers to the C++ simmer core are no longer valid when the parallelised execution ends. Thus, it is necessary to extract the results for each thread at the end of the execution. This can be done with the helper function `wrap` as follows. ```{r} library(parallel) envs <- mclapply(1:100, function(i) { simmer("SuperDuperSim") %>% add_resource("nurse", 1) %>% add_resource("doctor", 2) %>% add_resource("administration", 1) %>% add_generator("patient", patient, function() rnorm(1, 10, 2)) %>% run(80) %>% wrap() }) ``` This helper function brings the simulation data back to R and makes it accessible through the same methods that would ordinarily be used for a `simmer` environment. ```{r, message=FALSE} envs[[1]] %>% get_n_generated("patient") envs[[1]] %>% get_queue_count("doctor") envs[[1]] %>% get_queue_size("doctor") envs %>% get_mon_resources() %>% head() envs %>% get_mon_arrivals() %>% head() ``` Unfortunately, as the C++ simulation cores are destroyed, the downside of this kind of parallelization is that one cannot resume execution of the replicas. ## Basic visualisation tools You may want to try the `simmer.plot` package, a plugin for `simmer` that provides some basic visualisation tools to help you take a quick glance at your simulation results or debug a trajectory object: - [_Plotting simmer statistics_](https://r-simmer.org/extensions/plot/articles/plot.simmer.html) - [_Plotting simmer trajectories_](https://r-simmer.org/extensions/plot/articles/plot.trajectory.html).