library(simmer)
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 activities in the specified order and, eventually, leave the system. Consider the following:
<- trajectory() %>%
traj 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.
Usage examples are provided in the help page for each activity. The complete set of activities can be found and navigated in the reference page, or you can list them as follows:
methods(class="trajectory")
#> [1] activate batch branch
#> [4] clone deactivate get_n_activities
#> [7] handle_unfinished join leave
#> [10] length log_ plot
#> [13] print release_all release_selected_all
#> [16] release_selected release renege_abort
#> [19] renege_if renege_in rep
#> [22] rollback seize_selected seize
#> [25] select send separate
#> [28] set_attribute set_capacity_selected set_capacity
#> [31] set_global set_prioritization set_queue_size_selected
#> [34] set_queue_size set_source set_trajectory
#> [37] stop_if synchronize timeout_from_attribute
#> [40] timeout [ [<-
#> [43] [[ [[<- trap
#> [46] untrap wait
#> see '?methods' for accessing help and source code
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
).
Many activities accept functions as arguments to be evaluated dynamically during the simulation. For example, see help(timeout)
:
task: the timeout duration supplied by either passing a numeric or a callable object (a function) which must return a numeric.
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 once when the trajectory is defined, and will remain static after that.
trajectory() %>%
timeout(rexp(1, 10)) %>% # fixed
timeout(function() rexp(1, 10)) # dynamic
#> trajectory: anonymous, 2 activities
#> { Activity: Timeout | delay: 0.0246759 }
#> { Activity: Timeout | delay: function() }
Of course, this task
, supplied as a function, may be as complex as you need and, for instance, it may check the status of a particular resource, interact with other entities in your simulation model… The same applies to all the activities when they accept a function as a parameter.
Dynamic arguments 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:
<- trajectory() %>%
traj log_(function() as.character(now(env)))
<- simmer() %>%
env add_generator("dummy", traj, function() 1) %>%
run(4)
#> 1: dummy0: 2.50175860496223
#> 2: dummy1: 2.50175860496223
#> 3: dummy2: 2.50175860496223
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:
<- trajectory() %>%
traj log_(function() as.character(now(env)))
<- simmer() %>%
env add_generator("dummy", traj, function() 1)
%>% run(4) %>% invisible
env #> 1: dummy0: 1
#> 2: dummy1: 2
#> 3: dummy2: 3
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:
# first, instantiate the environment
<- simmer()
env
# here I'm using it
<- trajectory() %>%
traj log_(function() as.character(now(env)))
# and finally, run it
%>%
env add_generator("dummy", traj, function() 1) %>%
run(4) %>% invisible
#> 1: dummy0: 1
#> 2: dummy1: 2
#> 3: dummy2: 3
The join(...)
method is very useful to concatenate together any number of trajectories. It may be used as a standalone function as follows:
<- trajectory() %>% seize("dummy", 1)
t1 <- trajectory() %>% timeout(1)
t2 <- trajectory() %>% release("dummy", 1)
t3
<- join(t1, t2, t3)
t0
t0#> trajectory: anonymous, 3 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Timeout | delay: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
Or it may operate inline, like another activity:
<- trajectory() %>%
t0 join(t1) %>%
timeout(1) %>%
join(t3)
t0#> trajectory: anonymous, 3 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Timeout | delay: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
You can think about a trajectory object as a list of activities that has a length
length(t0)
#> [1] 3
and can be subset using the standard operator [
. For instance, you can select the activities you want with a logical vector:
c(TRUE, FALSE, TRUE)]
t0[#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
Or a set of indices that respect the order given:
c(1, 3)]
t0[#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
c(3, 1)]
t0[#> trajectory: anonymous, 2 activities
#> { Activity: Release | resource: dummy, amount: 1 }
#> { Activity: Seize | resource: dummy, amount: 1 }
Or a set of indices to remove from the selection:
-2]
t0[#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
Or by name, but note that this does not respect the order given though, because it performs a match:
c("seize", "release")]
t0[#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
c("release", "seize")]
t0[#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
If you provide no indices, the whole trajectory is returned:
t0[]#> trajectory: anonymous, 3 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Timeout | delay: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
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:
head(t0, 2)
#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Timeout | delay: 1 }
tail(t0, -1)
#> trajectory: anonymous, 2 activities
#> { Activity: Timeout | delay: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
The [[
operator can also be used to extract only one element:
2]]
t0[[#> trajectory: anonymous, 1 activities
#> { Activity: Timeout | delay: 1 }
which is equivalent to t0[2]
. If a string is provided, it ensures that only the first match is returned:
join(t0, t0)["timeout"]
#> trajectory: anonymous, 2 activities
#> { Activity: Timeout | delay: 1 }
#> { Activity: Timeout | delay: 1 }
join(t0, t0)[["timeout"]]
#> trajectory: anonymous, 1 activities
#> { Activity: Timeout | delay: 1 }