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:

library(simmer)

patient_traj<-
  create_trajectory(name = "patient_trajectory") %>%
  seize(resource = "doctor", amount = 1) %>%
  timeout(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.

set_attribute

The set_attribute(trajectory, key, value) function set the value of an arrival’s attribute key. Be aware that value can only be numeric.

patient_traj<-
  create_trajectory(name = "patient_trajectory") %>%
  set_attribute("my_key", 123) %>%
  timeout(5) %>%
  set_attribute("my_key", 456)

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

get_mon_attributes(env)
##   time     name    key value
## 1    0 patient0 my_key   123
## 2    5 patient0 my_key   456

Above, a trajectory which only sets attribute my_key to value 123 is launched once by an arrival generated at time 0 (check ?at). 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. In fact, if, instead of a numeric value, you supply a function with one parameter, the current set of attributes is passed as a list to that function. Whatever (numeric value) your function returns is set as the value of the specified attribute key. If the supplied function has no parameters, it is evaluated in the same way, but the attribute list is not accesible in the function body. This means that, if you supply a function to the value parameter, it has to be in the form of either function(attrs){} (first case) or function(){} (second case). Below, you can see an example of this in practice.

patient_traj<-
  create_trajectory(name = "patient_trajectory") %>%
  set_attribute("my_key", 123) %>%
  timeout(5) %>%
  set_attribute("my_key", function(attrs) attrs[["my_key"]] + 1) %>%
  timeout(5) %>%
  set_attribute("dependent_key", function(attrs) ifelse(attrs[["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) %>%
  run()

get_mon_attributes(env)
##   time     name             key       value
## 1    0 patient0          my_key 123.0000000
## 2    5 patient0          my_key 124.0000000
## 3   10 patient0   dependent_key   0.0000000
## 4   15 patient0 independent_key   0.7143711

seize & release

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

Consider the following example:

patient_traj<-
  create_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)
##   time server queue system resource
## 1    0      0     0      0   doctor
## 2    3      1     0      1   doctor

Here the mon=1 argument (=default) of add_resource makes the simulation environment monitor the resource usage. Using the get_mon_resources(env) function 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 in the form of either function(){} or function(attrs){} to the amount parameter instead of a numeric value. If going for the latter, the current state of the arrival’s attributes will be passed to attrs as a list which you can inspect. This allows for the following:

patient_traj<-
  create_trajectory(name = "patient_trajectory") %>%
  set_attribute("health", function() sample(20:80, 1)) %>%
  set_attribute("docs_to_seize", function(attrs) ifelse(attrs[["health"]]<50, 1, 2)) %>%
  seize("doctor", function(attrs) attrs[["docs_to_seize"]]) %>%
  timeout(3) %>%
  release("doctor", function(attrs) attrs[["docs_to_seize"]])

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

get_mon_resources(env)
##   time server queue system resource
## 1    0      0     0      0   doctor
## 2    3      1     0      1   doctor
get_mon_attributes(env)
##   time     name           key value
## 1    0 patient0        health    20
## 2    0 patient0 docs_to_seize     1

timeout

At its simplest, the timeout(trajectory, task) function 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.

patient_traj<-
  create_trajectory(name = "patient_trajectory") %>%
  timeout(3)

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

get_mon_arrivals(env)
##       name start_time end_time activity_time finished
## 1 patient0          0        3             3     TRUE

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 in to form of either function(){} or function(attrs){} to the task parameter. In the following example this functionality is demonstrated:

patient_traj<-
  create_trajectory(name = "patient_trajectory") %>%
  set_attribute("health", function() sample(20:80, 1)) %>%
  # distribution-based timeout
  timeout(function() rpois(1, 10)) %>%
  # attribute-dependent timeout
  timeout(function(attrs) (100 - attrs[["health"]]) * 2)

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

get_mon_arrivals(env)
##       name start_time end_time activity_time finished
## 1 patient0          0      118           118     TRUE
get_mon_attributes(env)
##   time     name    key value
## 1    0 patient0 health    50

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() rpois(1, 10)), rpois(1, 10) will be evaluated every time the timeout activity is executed. However, if you supply it in the form of timeout(rpois(1, 10)), it will only be evaluated at 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, check a resource’s status, interact with other entities in your simulation model… The same applies to all previous activities when they accept a function as a parameter.

branch

in progress…

rollback

The rollback(trajectory, amount, times, check) function 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.

t0<-create_trajectory() %>%
  timeout(function(){
    print("Hello!")
    0}) %>%
  rollback(amount=1, times=3)


simmer() %>%
  add_generator("hello_sayer", t0, at(0)) %>% 
  run()
## [1] "Hello!"
## [1] "Hello!"
## [1] "Hello!"
## [1] "Hello!"
## Simmer environment: anonymous | now: 0 | next: Inf
## { Generator: hello_sayer | monitored: 1 | n_generated: 1 }

The rollback function 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:

t0<-create_trajectory() %>%
  set_attribute("happiness", 0) %>%
  # the timeout function is used simply to print something and returns 0,
  # hence it is a dummy timeout
  timeout(function(attrs){
    cat(">> Happiness level is at: ", attrs[["happiness"]], " -- ")
    cat(ifelse(attrs[["happiness"]]<25,"PETE: I'm feeling crappy...",
           ifelse(attrs[["happiness"]]<50,"PETE: Feelin' a bit moody",
                  ifelse(attrs[["happiness"]]<75,"PETE: Just had a good espresso",
                         "PETE: Let's do this! (and stop this loop...)")))
    , "\n")
    return(0)
  }) %>%
  set_attribute("happiness", function(attrs) attrs[["happiness"]] + 25) %>%
  rollback(amount=2, check=function(attrs) attrs[["happiness"]] < 100)


simmer() %>%
  add_generator("mood_swinger", t0, at(0)) %>% 
  run()
## >> Happiness level is at:  0  -- PETE: I'm feeling crappy... 
## >> Happiness level is at:  25  -- PETE: Feelin' a bit moody 
## >> Happiness level is at:  50  -- PETE: Just had a good espresso 
## >> Happiness level is at:  75  -- PETE: Let's do this! (and stop this loop...)
## Simmer environment: anonymous | now: 0 | next: Inf
## { Generator: mood_swinger | monitored: 1 | n_generated: 1 }

Bringing it all together

in progress…