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.
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.8810414
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 2 0 2 doctor
get_mon_attributes(env)
## time name key value
## 1 0 patient0 health 73
## 2 0 patient0 docs_to_seize 2
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 87 87 TRUE
get_mon_attributes(env)
## time name key value
## 1 0 patient0 health 62
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.
in progress…
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 }
in progress…