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:
traj <- 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.
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_from_global timeout [
#> [43] [<- [[ [[<-
#> [46] trap 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:
traj <- trajectory() %>%
log_(function() as.character(now(env)))
env <- simmer() %>%
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:
traj <- trajectory() %>%
log_(function() as.character(now(env)))
env <- simmer() %>%
add_generator("dummy", traj, function() 1)
env %>% run(4) %>% invisible
#> 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:
The join(...)
method is very useful to concatenate
together any number of trajectories. It may be used as a standalone
function as follows:
t1 <- trajectory() %>% seize("dummy", 1)
t2 <- trajectory() %>% timeout(1)
t3 <- trajectory() %>% release("dummy", 1)
t0 <- join(t1, t2, t3)
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:
t0 <- trajectory() %>%
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
and can be subset using the standard operator [
. For
instance, you can select the activities you want with a logical
vector:
t0[c(TRUE, FALSE, TRUE)]
#> 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:
t0[c(1, 3)]
#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
t0[c(3, 1)]
#> 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:
t0[-2]
#> 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:
t0[c("seize", "release")]
#> trajectory: anonymous, 2 activities
#> { Activity: Seize | resource: dummy, amount: 1 }
#> { Activity: Release | resource: dummy, amount: 1 }
t0[c("release", "seize")]
#> 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:
which is equivalent to t0[2]
. If a string is provided,
it ensures that only the first match is returned: