---
title: "Advanced Trajectory Usage"
author: "Iñaki Ucar, Bart Smeets"
date: "`r Sys.Date()`"
output: 
  rmarkdown::html_vignette:
    toc: yes
vignette: >
  %\VignetteIndexEntry{03. Advanced Trajectory Usage}
  %\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")

required <- c("simmer.plot")

if (!all(sapply(required, requireNamespace, quietly = TRUE)))
  knitr::opts_chunk$set(eval = FALSE)
```

```{r, message=FALSE}
library(simmer)
library(simmer.plot)
```

## Interaction with the environment

Many activities accept functions as arguments to be evaluated dynamically during the simulation. Those functions may interact with the environment to extract parameters of interest such as the current simulation time (see `?now`), status of resources (see `?get_capacity`), status of generators (see `?get_n_generated`), or directly to gather the history of monitored values (see `?get_mon`). The only requirement is that the simulation environment must be in the scope of the trajectory.

Therefore, this will not work:

```{r, error = TRUE}
t <- trajectory() %>%
  log_(function() as.character(now(env)))

env <- simmer() %>%
  add_generator("dummy", t, function() 1) %>%
  run(4)
```

because the global `env` is not available at runtime: the simulation runs _and then_ the resulting object is assigned to `env`. For `env` to be in the scope of `t` during this simulation, it is enough to detach the `run()` method from the definition pipe:

```{r}
t <- trajectory() %>%
  log_(function() as.character(now(env)))

env <- simmer() %>%
  add_generator("dummy", t, function() 1)

env %>% run(4) %>% invisible
```

And we get the expected output. However, as a general rule of good practice, __it is recommended to instantiate the environment always in the first place__ to avoid possible mistakes, and because the code becomes more readable:

```{r}
# First, instantiate the environment
env <- simmer()

# Here I'm using it
t <- trajectory() %>%
  log_(function() as.character(now(env)))

# And finally, run it
env %>%
  add_generator("dummy", t, function() 1) %>%
  run(4) %>% invisible
```

## Available set of activities

When a generator creates an arrival, it couples the arrival to a given trajectory. A trajectory is defined as an interlinkage of activities which together form the arrivals' lifetime in the system. Once an arrival is coupled to the trajectory, it will (in general) start processing the activities in the trajectory in the specified order and, eventually, leave the system. Consider the following:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  seize(resource = "doctor", amount = 1) %>%
  timeout(task = 3) %>%
  release(resource = "doctor", amount = 1)
```

Here we create a trajectory where a patient _seizes_ a doctor for 3 minutes and then _releases_ him again. This is a very straightforward example, however, most of the trajectory-related functions allow for more advanced usage. The different functions are introduced below.

Additionally, you may want to try the `simmer.bricks` package, a plugin for `simmer` which provides helper methods for trajectories. Each *brick* wraps a common activity pattern that can be used to build trajectories more conveniently (see the [_Introduction to `simmer.bricks`_](http://r-simmer.org/extensions/bricks/articles/introduction.html)).

### `log_()`

The `log_(., message, level`) method just prints a given message preceded by the simulation time and the name of the arrival, for debugging purposes. The logging level is defined in the simulation environment:

```{r}
t <- trajectory() %>%
  log_("this is always printed") %>% # level = 0 by default
  log_("this is printed if `log_level>=1`", level = 1) %>%
  log_("this is printed if `log_level>=2`", level = 2)

simmer() %>%
  add_generator("dummy", t, at(0)) %>%
  run() %>% invisible

simmer(log_level = 1) %>%
  add_generator("dummy", t, at(0)) %>%
  run() %>% invisible

simmer(log_level = Inf) %>%
  add_generator("dummy", t, at(0)) %>%
  run() %>% invisible
```

### `set_attribute()`, `set_global()`

The `set_attribute(., keys, values)` method sets the `values` of an arrival's attributes `keys`. Both `keys` and `values` can be vectors (and their lengths must match) or functions returning vectors. But either way, the `values` stored can only be numeric.

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  set_attribute(keys = "my_key", values = 123) %>%
  timeout(5) %>%
  set_attribute(keys = "my_key", values = 456)

env <- simmer() %>%
  add_generator("patient", patient_traj, at(0), mon = 2) %>%
  run()

get_mon_attributes(env)
```

Above, a trajectory which _only_ sets attribute `my_key` to value `123` is launched once by an arrival generated at time 0 (check `?at`). The `mon=2` of `add_generator()` makes the simulation environment monitor the attributes' evolution (disabled by default). Using `get_mon_attributes()`, we can look at the evolution of the value of `my_key`.

If you want to set an attribute that depends on another attribute, or on the current value of the attribute to be set, this is also possible. Attributes can be retrieved inside user-supplied functions using `get_attribute(., keys)`. Below, you can see an example of this in practice.

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  set_attribute("my_key", 123) %>%
  timeout(5) %>%
  set_attribute("my_key", 1, mod="+") %>%
  timeout(5) %>%
  set_attribute("dependent_key", function() ifelse(get_attribute(env, "my_key")<=123, 1, 0)) %>%
  timeout(5) %>%
  set_attribute("independent_key", function() runif(1))

env<- simmer() %>%
  add_generator("patient", patient_traj, at(0), mon = 2)
env %>% run()

get_mon_attributes(env)
```

Attributes are _per arrival_, meaning that each arrival has its own set of attributes, not visible by anyone else:

```{r}
writer <- trajectory() %>%
  set_attribute(keys = "my_key", values = 123)

reader <- trajectory() %>%
  log_(function() paste0(get_attribute(env, "my_key")))

env <- simmer() %>%
  add_generator("writer", writer, at(0), mon = 2) %>%
  add_generator("reader", reader, at(1), mon = 2)
env %>% run()

get_mon_attributes(env)
```

Thus, the reader in the example above returns `NA`, missing. However, attributes can also be global with `set_global(., keys, values)`:

```{r}
writer <- trajectory() %>%
  set_global(keys = "my_key", values = 123) 

reader <- trajectory() %>%
  log_(function() paste0(get_attribute(env, "my_key"), ", ", 
                         get_global(env, "my_key")))

env <- simmer() %>%
  add_generator("writer", writer, at(0), mon = 2) %>%
  add_generator("reader", reader, at(1), mon = 2)
env %>% run()

get_mon_attributes(env)
```

As shown above, global attributes are reported in `get_mon_attributes()` as unnamed key/value pairs.

### `timeout()`, `timeout_from_attribute()`

At its simplest, the `timeout(., task)` method delays the arrival's advance through the trajectory for a specified amount of time. Consider the following minimal example where we simply supply a static value to the timeout's `task` parameter.

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  timeout(task = 3)

env <- simmer() %>%
  add_generator("patient", patient_traj, at(0)) %>%
  run()

get_mon_arrivals(env)
```

Often, however, you want a timeout to be dependent on a distribution or, for example, an earlier set attribute. This is achieved by passing a function to the `task` parameter, as we explained before, and using `get_attribute(.)`. In the following example, this functionality is demonstrated:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  set_attribute("health", function() sample(20:80, 1)) %>%
  # distribution-based timeout
  timeout(function() rexp(1, 10)) %>%
  # attribute-dependent timeout
  timeout(function() (100 - get_attribute(env, "health")) * 2)

env <- simmer() %>%
  add_generator("patient", patient_traj, at(0), mon = 2)
env %>% run()

get_mon_arrivals(env)
get_mon_attributes(env)
```

Be aware that if you want the `timeout()`'s `task` parameter to be evaluated dynamically, you should supply a callable function. For example in `timeout(function() rexp(1, 10))`, `rexp(1, 10)` will be evaluated every time the timeout activity is executed. However, if you supply it in the form of `timeout(rexp(1, 10))`, it will only be evaluated at the initalization and will remain static after that.

Of course, this `task`, supplied as a function, may be as complex as you need and, for instance, it may check a resource's status, interact with other entities in your simulation model... The same applies to all the activities when they accept a function as a parameter.

If you simply need a delay previously set as an attribute, you may use `timeout_from_attribute(., key)` (or `timeout_from_global(., key)` for a global attribute). Thus, the following timeouts are equivalent, but the second one is much simpler and faster:

```{r}
traj <- trajectory() %>%
  set_attribute("delay", 2) %>%
  timeout(function() get_attribute(env, "delay")) %>%
  log_("first timeout") %>%
  timeout_from_attribute("delay") %>%
  log_("second timeout")

env <- simmer() %>%
  add_generator("dummy", traj, at(0))
env %>% run() %>% invisible
```

### `seize()`, `release()`

The `seize(., resource, amount)` method seizes a specified `amount` of the resource named `resource`. Conversely, the `release(., resource, amount)` method releases a specified `amount` of the resource named `resource`. Be aware that, in order to use these functions in relation to a specific resource, you have to create it in your definition of the simulation environment (check `?add_resource`). 

Consider the following example:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  seize(resource = "doctor", amount = 1) %>%
  timeout(3) %>%
  release(resource = "doctor", amount = 1)

env <- simmer() %>%
  add_resource("doctor", capacity=1, mon = 1) %>%
  add_generator("patient", patient_traj, at(0)) %>%
  run()

get_mon_resources(env)
```

Here the `mon=1` argument (=default) of `add_resource()` makes the simulation environment monitor the resource usage. Using the `get_mon_resources(env)` method you can get access to the log of the usage evolution of resources.

There are situations where you want to let the amount of resources seized/released be dependent on a specific function or on a previously set attribute. To achieve this, you can pass a function to the `amount` parameter instead of a numeric value and use `get_attribute(.)`:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  set_attribute("health", function() sample(20:80, 1)) %>%
  set_attribute("docs_to_seize", function() ifelse(get_attribute(env, "health")<50, 1, 2)) %>%
  seize("doctor", function() get_attribute(env, "docs_to_seize")) %>%
  timeout(3) %>%
  release("doctor", function() get_attribute(env, "docs_to_seize"))

env <- simmer() %>%
  add_resource("doctor", capacity = 2, mon = 1) %>%
  add_generator("patient", patient_traj, at(0), mon = 2)
env %>% run()

get_mon_resources(env)
get_mon_attributes(env)
```

By default, an unsuccessful `seize()` results in the rejection of the arrival. In the following example, the second patient tries to seize the only doctor while the first patient is being attended. There is no waiting room available, therefore it is rejected:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  log_("arriving...") %>%
  seize("doctor", 1) %>%
  # the second patient won't reach this point
  log_("doctor seized") %>%
  timeout(5) %>%
  release("doctor", 1)

env <- simmer() %>%
  add_resource("doctor", capacity = 1, queue_size = 0) %>%
  add_generator("patient", patient_traj, at(0, 1)) %>%
  run()

get_mon_arrivals(env)
get_mon_resources(env)
```

Sometimes, you don't want to reject an unsuccessful `seize()`, but to follow another path. Let's modify the example above to enable the second patient to visit a nurse instead:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  log_("arriving...") %>%
  seize("doctor", 1, continue = FALSE,
        reject = trajectory("rejected patient") %>%
          log_("rejected!") %>%
          seize("nurse", 1) %>%
          log_("nurse seized") %>%
          timeout(2) %>%
          release("nurse", 1)) %>%
  # the second patient won't reach this point
  log_("doctor seized") %>%
  timeout(5) %>%
  release("doctor", 1)

env <- simmer() %>%
  add_resource("doctor", capacity = 1, queue_size = 0) %>%
  add_resource("nurse", capacity = 10, queue_size = 0) %>%
  add_generator("patient", patient_traj, at(0, 1)) %>%
  run()

get_mon_arrivals(env)
get_mon_resources(env)
```

The flag `continue` indicates whether the `reject` sub-trajectory should be connected to the main trajectory or not. In this case, with `continue=FALSE`, the rejected arrival seizes the nurse and its lifetime ends after releasing him/her. Otherwise, it would keep executing activities in the main trajectory.

Note that the second patient may also keep trying if he/she must see the doctor:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  log_("arriving...") %>%
  seize("doctor", 1, continue = FALSE,
        reject = trajectory("rejected patient") %>%
          log_("rejected!") %>%
          # go for a walk and try again
          timeout(2) %>%
          log_("retrying...") %>%
          rollback(amount = 4, times = Inf)) %>%
  # the second patient will reach this point after a couple of walks
  log_("doctor seized") %>%
  timeout(5) %>%
  release("doctor", 1) %>%
  log_("leaving")

env <- simmer() %>%
  add_resource("doctor", capacity = 1, queue_size = 0) %>%
  add_generator("patient", patient_traj, at(0, 1)) %>%
  run()

get_mon_arrivals(env)
get_mon_resources(env)
```

There is another optional sub-trajectory called `post.seize` and, as its name suggests, it is executed after a successful `seize()`. Thus, you can do the following:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  log_("arriving...") %>%
  seize("doctor", 1, continue = c(TRUE, TRUE),
        post.seize = trajectory("admitted patient") %>%
          log_("admitted") %>%
          timeout(5) %>%
          release("doctor", 1),
        reject = trajectory("rejected patient") %>%
          log_("rejected!") %>%
          seize("nurse", 1) %>%
          timeout(2) %>%
          release("nurse", 1)) %>%
  # both patients will reach this point, as continue = c(TRUE, TRUE)
  timeout(10) %>%
  log_("leaving...")

env <- simmer() %>%
  add_resource("doctor", capacity = 1, queue_size = 0) %>%
  add_resource("nurse", capacity = 10, queue_size = 0) %>%
  add_generator("patient", patient_traj, at(0, 1)) %>%
  run()

get_mon_arrivals(env)
get_mon_resources(env)
```

### `set_capacity()`, `set_queue_size()`

The `set_capacity(., resource, value)` method modifies the capacity of the specified resource by name, and the `set_queue_size(., resource, value)` method modifies its queue size. Be aware that, in order to use these functions in relation to a specific resource, you have to create it in your definition of the simulation environment (check `?add_resource`). You can supply the `value` parameter either as a numeric value or as a function which must return a numeric value.

These activities are interesting to introduce dynamical changes in the resources. For instance, consider the following example in which two trajectories fight for the capacity of their resource:

```{r}
set.seed(12345)

t1 <- trajectory() %>%
  seize("res1", 1) %>%
  set_capacity(resource = "res1", value = 1, mod="+") %>%
  set_capacity(resource = "res2", value = -1, mod="+") %>%
  timeout(function() rexp(1, 1)) %>%
  release("res1", 1)

t2 <- trajectory() %>%
  seize("res2", 1) %>%
  set_capacity(resource = "res2", value = 1, mod="+") %>%
  set_capacity(resource = "res1", value = -1, mod="+") %>%
  timeout(function() rexp(1, 1)) %>%
  release("res2", 1)

env <- simmer() %>%
  add_resource("res1", capacity = 20, queue_size = Inf) %>%
  add_resource("res2", capacity = 20, queue_size = Inf) %>%
  add_generator("t1_", t1, function() rexp(1, 1)) %>%
  add_generator("t2_", t2, function() rexp(1, 1)) %>%
  run(100)

plot(get_mon_resources(env), "usage", c("res1", "res2"), steps = TRUE)
```

### `select()`

`seize()`, `release()`, `set_capacity()` and `set_queue_size()` work well when you know the resources implied beforehand. But sometimes the resource to choose may depend on a certain policy. For these situations, the `select(., resources, policy, id)` method offers the possibility of selecting a resource at any point, and this choice will be observed by `seize_selected()`, `release_selected()`, `set_capacity_selected()` and `set_queue_size_selected()`:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  select(resources = c("doctor1", "doctor2", "doctor3"), policy = "round-robin") %>%
  set_capacity_selected(1) %>%
  seize_selected(amount = 1) %>%
  timeout(5) %>%
  release_selected(amount = 1)

env <- simmer() %>%
  add_resource("doctor1", capacity = 0) %>%
  add_resource("doctor2", capacity = 0) %>%
  add_resource("doctor3", capacity = 0) %>%
  add_generator("patient", patient_traj, at(0, 1, 2)) %>%
  run()

get_mon_arrivals(env)
get_mon_resources(env)
```

If you provide `select()` with `resources` as a vector of names, you can use one of the predefined policies (see `?select`). If you need some custom policy, you can define it and supply it as a function. For instance, let's pick a resource based on a previously set attribute:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  set_attribute("resource", function() sample(1:3, 1)) %>%
  select(resources = function() paste0("doctor", get_attribute(env, "resource"))) %>%
  seize_selected(amount = 1) %>%
  timeout(5) %>%
  release_selected(amount = 1)

env <- simmer() %>%
  add_resource("doctor1", capacity = 1) %>%
  add_resource("doctor2", capacity = 1) %>%
  add_resource("doctor3", capacity = 1) %>%
  add_generator("patient", patient_traj, at(0, 1, 2), mon = 2)
env %>% run()

get_mon_attributes(env)
get_mon_arrivals(env)
get_mon_resources(env)
```

And, of course, everything learned for `seize()`, `release()`, `set_capacity()` and `set_queue_size()` applies to all their `_selected` versions.

### `activate()`, `deactivate()`

The `activate(., source)` and `deactivate(., source)` methods are able to start or stop, respectively, a source by name. This name can be supplied as either a string or a function returning a string. In the following simple example, the use of these methods spreads the interarrival times which otherwise would be of 1 unit of time, as defined in the source:

```{r}
t <- trajectory() %>%
  deactivate(source = "dummy") %>%
  timeout(1) %>%
  activate(source = "dummy")

simmer() %>%
  add_generator("dummy", t, function() 1) %>%
  run(10) %>%
  get_mon_arrivals()
```

### `set_trajectory()`, `set_source()`

The `set_trajectory(., source, trajectory)` and `set_source(., source, object)` methods are able to install a new trajectory or source object (function or data frame, depending on the source type), respectively, in a source by name. This name can be supplied as either a string or a function returning a string.

In the following distribution, `t2` changes the distribution and then switches to `t1`, so that only the first arrival takes `t2`:

```{r}
t1 <- trajectory() %>%
  timeout(1)

t2 <- trajectory() %>%
  set_source("dummy", function() 1) %>%
  set_trajectory("dummy", t1) %>%
  timeout(2)

simmer() %>%
  add_generator("dummy", trajectory = t2, distribution = function() 2) %>%
  run(10) %>%
  get_mon_arrivals()
```

### `set_prioritization()`

The `add_generator()` method assigns a set of prioritization values to each generated arrival: by default, `priority=0`, `preemptible=priority`, `restart=FALSE` (see `?add_generator` for more details). The `set_prioritization(., values)` method can change those values with more granularity at any point in the trajectory, and they can also be retrieved inside the trajectory using `get_prioritization(.)`:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  set_attribute("priority", 3) %>%
  # static values
  set_prioritization(values = c(3, 7, TRUE)) %>%
  # dynamically with a function
  set_prioritization(values = function() {
    prio <- get_prioritization(env)
    attr <- get_attribute(env, "priority")
    c(attr, prio[[2]]+1, FALSE)
  })
```

More details on prioritization in the _Advanced Resource Usage_ vignette (pending).

### `branch()`

The `branch(., option, continue, ...)` method offers the possibility of adding alternative paths in the trajectory. The following example shows how a trajectory can be built with a 50-50 chance for an arrival to pass through each path of a two-path branch.

```{r}
t1 <- trajectory("trajectory with a branch") %>%
  seize("server", 1) %>%
  branch(option = function() sample(1:2, 1), continue = c(T, F), 
         trajectory("branch1") %>%
           timeout(function() 1),
         trajectory("branch2") %>%
           timeout(function() rexp(1, 3)) %>%
           release("server", 1)
  ) %>%
  release("server", 1)
```

When an arrival gets to the branch, the first argument is evaluated to select a specific path to follow, so it must be callable and must return a numeric value between 1 and `n`, where `n` is the number of paths defined. The second argument, `continue`, indicates whether the arrival must continue executing the activities after the selected path or not. In the example above, only the first path continues to the last `release()`.

Sometimes you may need to count how many times a certain trajectory in a certain branch is entered, or how much time arrivals spend inside that trajectory. For these situations, it is handy to use resources with infinite capacity just for _accounting_ purposes, like in the example below.

```{r, message = FALSE}
t0 <- trajectory() %>%
  branch(function() sample(c(1, 2), 1), continue = c(T, T),
         trajectory() %>%
           seize("branch1", 1) %>%
           # do stuff here
           timeout(function() rexp(1, 1)) %>%
           release("branch1", 1),
         trajectory() %>%
           seize("branch2", 1) %>%
           # do stuff here
           timeout(function() rexp(1, 1/2)) %>%
           release("branch2", 1))

env <- simmer() %>%
  add_generator("dummy", t0, at(rep(0, 1000))) %>%
  # Resources with infinite capacity, just for accounting purposes
  add_resource("branch1", Inf) %>%
  add_resource("branch2", Inf) %>%
  run()

arrivals <- get_mon_arrivals(env, per_resource = T)

# Times that each branch was entered
table(arrivals$resource)

# The `activity_time` is the total time inside each branch for each arrival
# Let's see the distributions
ggplot(arrivals) + geom_histogram(aes(x=activity_time)) + facet_wrap(~resource)
```

### `rollback()`

The `rollback(., amount, times, check)` method allows an arrival to rollback the trajectory an `amount` number of steps.

Consider the following where a string is printed in the timeout function. After the first run, the trajectory is rolled back 3 times (so that it prints "Hello!" _four_ times).

```{r}
t0 <- trajectory() %>%
  log_("Hello!") %>%
  timeout(1) %>%
  rollback(amount = 2, times = 3)

simmer() %>%
  add_generator("hello_sayer", t0, at(0)) %>% 
  run() %>% invisible
```

The `rollback()` method also accepts an optional `check` parameter which overrides the default amount-based behaviour. This parameter must be a function that returns a logical value. Each time an arrival reaches the activity, this `check` is evaluated to determine whether the `rollback()` with `amount` steps must be performed or not. Consider the following example:

```{r}
t0 <- trajectory() %>%
  set_attribute("happiness", 0) %>%
  log_(function() {
    level <- get_attribute(env, "happiness")
    paste0(">> Happiness level is at: ", level, " -- ", 
           ifelse(level<25,"PETE: I'm feeling crappy...",
                  ifelse(level<50,"PETE: Feelin' a bit moody",
                         ifelse(level<75,"PETE: Just had a good espresso",
                                "PETE: Let's do this! (and stop this loop...)"))))
  }) %>%
  set_attribute("happiness", 25, mod="+") %>%
  rollback(amount = 2, check = function() get_attribute(env, "happiness") < 100)

env <- simmer() %>%
  add_generator("mood_swinger", t0, at(0))
env %>% run() %>% invisible()
```

### `leave()`

The `leave(., prob)` method allows an arrival to leave the trajectory with some probability:

```{r}
patient_traj <- trajectory(name = "patient_trajectory") %>%
  seize("nurse", 1) %>%
  timeout(3) %>%
  release("nurse", 1) %>%
  log_("before leave") %>%
  leave(prob = 1) %>%
  log_("after leave") %>%
  # patients will never seize the doctor
  seize("doctor", 1) %>%
  timeout(3) %>%
  release("doctor", 1)

env <- simmer() %>%
  add_resource("nurse", capacity=1) %>%
  add_resource("doctor", capacity=1) %>%
  add_generator("patient", patient_traj, at(0)) %>%
  run()

get_mon_resources(env)
```

And of course, this probability may be evaluated dynamically also:

```{r}
set.seed(1234)

patient_traj <- trajectory(name = "patient_trajectory") %>%
  seize("nurse", 1) %>%
  timeout(3) %>%
  release("nurse", 1) %>%
  log_("before leave") %>%
  leave(prob = function() runif(1) < 0.5) %>%
  log_("after leave") %>%
  # some patients will seize the doctor
  seize("doctor", 1) %>%
  timeout(3) %>%
  release("doctor", 1)

env <- simmer() %>%
  add_resource("nurse", capacity=1) %>%
  add_resource("doctor", capacity=1) %>%
  add_generator("patient", patient_traj, at(0, 1)) %>%
  run()

get_mon_arrivals(env)
get_mon_resources(env)
```

### `clone()`, `synchronize()`

The `clone(., n, ...)` method offers the possibility of replicating an arrival `n-1` times to be processed through up to `n` sub-trajectories in parallel. Then, the `synchronize(., wait, mon_all)` method synchronizes and removes replicas. By default, `synchronize()` waits for all of the replicas to arrive and allows the last one to continue:

```{r}
t <- trajectory() %>%
  clone(n = 3,
        trajectory("original") %>%
          timeout(1),
        trajectory("clone 1") %>%
          timeout(2),
        trajectory("clone 2") %>%
          timeout(3)) %>%
  synchronize(wait = TRUE) %>%
  timeout(0.5)

env <- simmer(verbose = TRUE) %>%
  add_generator("arrival", t, at(0)) %>%
  run()

get_mon_arrivals(env)
```

Note that the parameter `n` may also be a function. If there are more sub-trajectories than clones, the extra ones are ignored. If there are less sub-trajectories than clones, some clones will continue to the next activity directly:

```{r}
t <- trajectory() %>%
  clone(n = 3,
        trajectory("original") %>%
          timeout(1),
        trajectory("clone 1") %>%
          timeout(2)) %>%
  synchronize(wait = TRUE) %>%
  timeout(0.5)

env <- simmer(verbose = TRUE) %>%
  add_generator("arrival", t, at(0)) %>%
  run()

get_mon_arrivals(env)
```

The behaviour of `synchronize()` can be modified in order to let the first clone pass and remove the others by setting `wait=FALSE`:

```{r}
t <- trajectory() %>%
  clone(n = 3,
        trajectory("original") %>%
          timeout(1),
        trajectory("clone 1") %>%
          timeout(2),
        trajectory("clone 2") %>%
          timeout(3)) %>%
  synchronize(wait = FALSE) %>%
  timeout(0.5)

env <- simmer(verbose = TRUE) %>%
  add_generator("arrival", t, at(0)) %>%
  run()

get_mon_arrivals(env)
```

By default, `synchronize()` does not record information about the clones removed (`mon_all=FALSE`). However, if it is required, you can get it by setting `mon_all=TRUE`:

```{r}
t <- trajectory() %>%
  clone(n = 3,
        trajectory("original") %>%
          timeout(1),
        trajectory("clone 1") %>%
          timeout(2),
        trajectory("clone 2") %>%
          timeout(3)) %>%
  synchronize(wait = FALSE, mon_all = TRUE) %>%
  timeout(0.5)

env <- simmer(verbose = TRUE) %>%
  add_generator("arrival", t, at(0)) %>%
  run()

get_mon_arrivals(env)
```

### `batch()`, `separate()`

The `batch(., n, timeout, permanent, name, rule)` method offers the possibility of collecting a number of arrivals before they can continue processing as a block. Then, the `separate(.)` method splits a previously established non-permanent batch. This allows us to implement a rollercoaster process, for instance.

Let us consider a rollercoaster, with up to 10 places and a queue of 20 people, that lasts 5 minutes. We can model this problem as follows: 

```{r}
set.seed(1234)

t <- trajectory() %>%
  batch(10, timeout = 5, permanent = FALSE) %>%
  seize("rollercoaster", 1) %>%
  timeout(5) %>%
  release("rollercoaster", 1) %>%
  separate()

env <- simmer() %>%
  # capacity and queue_size are defined in batches of 10
  add_resource("rollercoaster", capacity = 1, queue_size = 2) %>%
  add_generator("person", t, function() rexp(1, 2)) %>%
  run(15)

get_mon_arrivals(env, per_resource = TRUE)
```

We can see above that 3 batches have been created. The first 10 people arrive within 3.8 minutes and goes into the rollercoaster. When the ride ends, at 8.8, there are only 6 people waiting, but the `batch()` timer (`timeout=5`) has run out, and another ride starts with them. These batches are non-permanent (`permanent=FALSE`), so that `separate()` can split them and people can go their separate ways.

The optional argument `rule` accepts a function to perform a fine-grained selection of which arrivals should be batched. For each particular arrival, it is batched if the function returns `TRUE`, or it simply continues otherwise. For instance, in the example above, we can prevent batching by returning always `FALSE`:

```{r}
t_batch <- trajectory() %>%
  batch(10, timeout = 5, permanent = FALSE, rule = function() FALSE) %>%
  seize("rollercoaster", 1) %>%
  timeout(5) %>%
  release("rollercoaster", 1) %>%
  separate()

t_nobatch <- trajectory() %>%
  seize("rollercoaster", 1) %>%
  timeout(5) %>%
  release("rollercoaster", 1)

set.seed(1234)

env_batch <- simmer() %>%
  # capacity and queue_size are defined in batches of 10
  add_resource("rollercoaster", capacity = 1, queue_size = 2) %>%
  add_generator("person", t_batch, function() rexp(1, 2)) %>%
  run(15)

set.seed(1234)

env_nobatch <- simmer() %>%
  # capacity and queue_size are defined in batches of 10
  add_resource("rollercoaster", capacity = 1, queue_size = 2) %>%
  add_generator("person", t_nobatch, function() rexp(1, 2)) %>%
  run(15)

get_mon_arrivals(env_batch, per_resource = TRUE)
get_mon_arrivals(env_nobatch, per_resource = TRUE)
```

By default, batches are unnamed (`name=""`), which makes them independent of one another. However, it may be interesting to feed a common batch from different trajectories. For instance, we can try this:

```{r}
t0 <- trajectory() %>%
  batch(2) %>%
  timeout(2) %>%
  separate()

t1 <- trajectory() %>%
  timeout(1) %>%
  join(t0)

env <- simmer(verbose = TRUE) %>%
  add_generator("t0_", t0, at(0)) %>%
  add_generator("t1_", t1, at(0)) %>%
  run()

get_mon_arrivals(env)
```

But we don't get the expected output because the arrivals are feeding two different batches. The arrival following `t1` join `t0` after the timeout, but effectively this is __a clone__ of `t0`, which means that the above definition is equivalent to the following:

```{r}
t0 <- trajectory() %>%
  batch(2) %>%
  timeout(2) %>%
  separate()

t1 <- trajectory() %>%
  timeout(1) %>%
  batch(2) %>%
  timeout(2) %>%
  separate()
```

Thus, arrivals following a different trajectory will end up in a different batch in general. Nonetheless, there is one way to share a common batch across `batch()` activities. This can be done by using a common name:

```{r}
t0 <- trajectory() %>%
  batch(2, name = "mybatch") %>%
  timeout(2) %>%
  separate()

t1 <- trajectory() %>%
  timeout(1) %>%
  batch(2, name = "mybatch") %>%
  timeout(2) %>%
  separate()

env <- simmer(verbose = TRUE) %>%
  add_generator("t0_", t0, at(0)) %>%
  add_generator("t1_", t1, at(0)) %>%
  run()

get_mon_arrivals(env)
```

Or, equivalently,

```{r}
t0 <- trajectory() %>%
  batch(2, name = "mybatch") %>%
  timeout(2) %>%
  separate()

t1 <- trajectory() %>%
  timeout(1) %>%
  join(t0)

env <- simmer(verbose = TRUE) %>%
  add_generator("t0_", t0, at(0)) %>%
  add_generator("t1_", t1, at(0)) %>%
  run()

get_mon_arrivals(env)
```

### `send()`, `trap()`, `untrap()`, `wait()`

These activities enable asynchronous programming. The `send(., signals, delay)` method broadcasts a signal or a list of signals to all the arrivals subscribed to them. Signals can be triggered immediately:

```{r}
t <- trajectory() %>%
  send(signals = c("signal1", "signal2"))

simmer(verbose = TRUE) %>%
  add_generator("signaler", t, at(0)) %>%
  run() %>% invisible
```

or after some delay:

```{r}
t <- trajectory() %>%
  send(signals = c("signal1", "signal2"), delay = 3)

simmer(verbose = TRUE) %>%
  add_generator("signaler", t, at(0)) %>%
  run() %>% invisible
```

Note that both arguments, `signals` and `delay`, can be functions, and therefore they can retrieve the arrival's attributes.

This is not very useful if nobody is listening. Arrivals can subscribe to signals and (optionally) assign a handler with the `trap(., signals, handler, interruptible)`. In the following example, an arrival subscribes to a signal and blocks until its reception using the `wait(.)` method.

```{r}
t_blocked <- trajectory() %>%
  trap("you shall pass") %>%
  log_("waiting...") %>%
  wait() %>%
  log_("continuing!")

t_signaler <- trajectory() %>%
  log_("you shall pass") %>%
  send("you shall pass")

simmer() %>%
  add_generator("blocked", t_blocked, at(0)) %>%
  add_generator("signaler", t_signaler, at(5)) %>%
  run() %>% invisible
```

Note that signals are ignored when an arrival is waiting in a resource's queue. The same applies inside a batch: all the signals subscribed before entering the batch are ignored. Thus, the following batch will block indifinitely:

```{r}
t_blocked <- trajectory() %>%
  trap("you shall pass") %>%
  log_("waiting inside a batch...") %>%
  batch(1) %>%
  wait() %>%
  log_("continuing!")

t_signaler <- trajectory() %>%
  log_("you shall pass") %>%
  send("you shall pass")

simmer() %>%
  add_generator("blocked", t_blocked, at(0)) %>%
  add_generator("signaler", t_signaler, at(5)) %>%
  run() %>% invisible
```

Upon a signal reception, the arrival stops the current activity and executes the handler if provided. Then, the execution returns to the activity following the point of the interruption:

```{r}
t_worker <- trajectory() %>%
  trap("you are free to go", 
       handler = trajectory() %>%
         log_("ok, I'm packing...") %>%
         timeout(1)
  ) %>%
  log_("performing a looong task...") %>%
  timeout(100) %>%
  log_("and I'm leaving!")

t_signaler <- trajectory() %>%
  log_("you are free to go") %>%
  send("you are free to go")

simmer() %>%
  add_generator("worker", t_worker, at(0)) %>%
  add_generator("signaler", t_signaler, at(5)) %>%
  run() %>% invisible
```

Finally, the `untrap(., signals)` method can be used to unsubscribe from signals:

```{r}
t_worker <- trajectory() %>%
  trap("you are free to go", 
       handler = trajectory() %>%
         log_("ok, I'm packing...") %>%
         timeout(1)
  ) %>%
  log_("performing a looong task...") %>%
  untrap("you are free to go") %>%
  timeout(100) %>%
  log_("and I'm leaving!")

t_signaler <- trajectory() %>%
  log_("you are free to go") %>%
  send("you are free to go")

simmer() %>%
  add_generator("worker", t_worker, at(0)) %>%
  add_generator("signaler", t_signaler, at(5)) %>%
  run() %>% invisible
```

Signal handlers can be interrupted as well by default, meaning that a handler may keep restarting if there are frequent enough signals:

```{r}
t_worker <- trajectory() %>%
  trap("you are free to go", 
       handler = trajectory() %>%
         log_("ok, I'm packing...") %>%
         timeout(1)
  ) %>%
  log_("performing a looong task...") %>%
  timeout(100) %>%
  log_("and I'm leaving!")

t_signaler <- trajectory() %>%
  log_("you are free to go") %>%
  send("you are free to go")

simmer() %>%
  add_generator("worker", t_worker, at(0)) %>%
  add_generator("signaler", t_signaler, from(5, function() 0.5)) %>%
  run(10) %>% invisible
```

If an uninterruptible handler is needed, this can be achieved by setting the proper flag:

```{r}
t_worker <- trajectory() %>%
  trap("you are free to go", 
       handler = trajectory() %>%
         log_("ok, I'm packing...") %>%
         timeout(1),
       interruptible = FALSE            # make it uninterruptible
  ) %>%
  log_("performing a looong task...") %>%
  timeout(100) %>%
  log_("and I'm leaving!")

t_signaler <- trajectory() %>%
  log_("you are free to go") %>%
  send("you are free to go")

simmer() %>%
  add_generator("worker", t_worker, at(0)) %>%
  add_generator("signaler", t_signaler, from(5, function() 0.5)) %>%
  run(10) %>% invisible
```

### `renege_in()`, `renege_if()`, `renege_abort()`

The `renege_in(., t, out)` method offers the possibility of setting a timeout after which the arrival will abandon the trajectory. After reneging, the arrival can follow an optional sub-trajectory `out`. The `renege_abort(.)` method cancels the latter. Together, they allows us, for instance, to model arrivals with limited patience. In the example below, `customer1` arrives at the bank, where there is only one busy clerk. He/she waits in the queue for 5 minutes and then leaves. 

```{r}
t <- trajectory(name = "bank") %>%
  log_("Here I am") %>%
  # renege in 5 minutes
  renege_in(5, 
            out = trajectory() %>%
              log_("Lost my patience. Reneging...")
  ) %>%
  seize("clerk", 1) %>%
  # stay if I'm being attended within 5 minutes
  renege_abort() %>%
  log_("I'm being attended") %>%
  timeout(10) %>%
  release("clerk", 1) %>%
  log_("Finished")

simmer() %>%
  add_resource("clerk", 1) %>%
  add_generator("customer", t, at(0, 1)) %>%
  run() %>% invisible
```

The same can be achieved with `renege_if(., signal, out)` if `customer0` sends a signal to `customer1` at `t=5`:

```{r}
t <- trajectory(name = "bank") %>%
  log_("Here I am") %>%
  # renege when "renege now" is received
  renege_if("renege now", 
            out = trajectory() %>%
              log_("Ok. Reneging...")
  ) %>%
  seize("clerk", 1) %>%
  # stay if I'm being attended within 5 minutes
  renege_abort() %>%
  log_("I'm being attended") %>%
  timeout(5) %>%
  log_("I say: renege now") %>%
  send("renege now") %>%
  timeout(5) %>%
  release("clerk", 1) %>%
  log_("Finished")

simmer() %>%
  add_resource("clerk", 1) %>%
  add_generator("customer", t, at(0, 1)) %>%
  run() %>% invisible
```

Note that, in contrast to `trap()`, reneging is triggered even if the arrival is in a queue or is part of a non-permanent batch.

## Trajectory toolbox: joining and subsetting

### `join()`

The `join(...)` method is very useful to concatenate together any number of trajectories. It may be used as a standalone function as follows:

```{r}
t1 <- trajectory() %>% seize("dummy", 1)
t2 <- trajectory() %>% timeout(1)
t3 <- trajectory() %>% release("dummy", 1)

t0 <- join(t1, t2, t3)
t0
```

Or it may operate inline, like another activity:

```{r}
t0 <- trajectory() %>%
  join(t1) %>%
  timeout(1) %>%
  join(t3)
t0
```

### `[`, `[[`

You can think about a trajectory object as a list of activities that has a length

```{r}
length(t0)
```

and can be subset using the standard operator `[`. For instance, you can select the activities you want with a logical vector:

```{r}
t0[c(TRUE, FALSE, TRUE)]
```

Or a set of indices that respect the order given:

```{r}
t0[c(1, 3)]
t0[c(3, 1)]
```

Or a set of indices to remove from the selection:

```{r}
t0[-2]
```

Or by name, but note that this does **not** respect the order given though, because it performs a match:

```{r}
t0[c("seize", "release")]
t0[c("release", "seize")]
```

If you provide no indices, the whole trajectory is returned:

```{r}
t0[]
```

In fact, you are **cloning** the trajectory with the latter command. It is equivalent to `t0[1:length(t0)]` or `join(t0)`.

The generics `head()` and `tail()` use the `[` operator under the hood, thus you can use them as well:

```{r}
head(t0, 2)
tail(t0, -1)
```

The `[[` operator can also be used to extract **only one** element:

```{r}
t0[[2]]
```

which is equivalent to `t0[2]`. If a string is provided, it ensures that only the first match is returned:

```{r}
join(t0, t0)["timeout"]
join(t0, t0)[["timeout"]]
```
