A process-oriented and trajectory-based Discrete-Event Simulation
(DES) package for R. It is designed as a generic yet powerful framework. The
architecture encloses a robust and fast simulation core written in 'C++' with
automatic monitoring capabilities. It provides a rich and flexible R API that
revolves around the concept of trajectory, a common path in the simulation
model for entities of the same type. Documentation about 'simmer' is provided
by several vignettes included in this package, via the paper by Ucar, Smeets
& Azcorra (2019,
handle_unfinished()
activity sets a drop-out trajectory for unfinished
arrivals, i.e., those dropped from a resource (due to preemption, resource
shrinkage or a rejected seize
) or those that leave
a trajectory (#178
addressing #177).release_all()
and release_selected_all()
activities automatically
retrieve the amount of resources seized and release it (#180 addressing #25).get_seized()
and get_seized_selected()
getters allow an arrival to
retrieve the amount of resources seized (#180 addressing #179).stop_if()
activity sets a conditional breakpoint (#181 addressing #100).Queueing Systems
vignette with a section about state-dependent
service rates.get_selected()
retrieves names of selected resources via the
select()
activity (#172 addressing #171).Simulator
interface for adding processes and resources (#162).
The responsibility of building the objects has been moved to the caller.add_global()
method to attach global attributes to a simulation
environment (#174 addressing #158).get_global()
to work outside trajectories (#170 addressing #165).rollback()
with an infinite amount (#173).reset()
to avoid overwriting the simulation environment (#175).get_sources()
and get_resources()
retrieve a character vector of
source/resource names defined in a simulation environment.get_trajectory()
retrieves a trajectory to which a given source is
attached.shortest-queue-available
,
round-robin-available
, random-available
(#156). These are the same as the
existing non-available
ones, but they exclude unavailable resources
(capacity set to zero). Thus, if all resources are unavailable, an error is
raised.-DRCPP_PROTECTED_EVAL
(Rcpp >= 0.12.17.4) as
-DRCPP_USE_UNWIND_PROTECT
(6d27671).-DBOOST_NO_AUTO_PTR
(70328b6).log_
print (7c2e3b1).when_activated()
convenience function to easily generate arrivals on
demand from trajectories (#161 closing #160).schedule
printing (9c66285).set_attribute(global=TRUE)
, get_attribute(global=TRUE)
and
timeout_from_attribute(global=TRUE)
(#164), the *_global
versions should
be used instead.simmer
license has been changed to GPL >= 2.inst/include
(#147 closing #145). Therefore, from now on it is possible to
extend the C++ API from another package by listing simmer
under the
LinkingTo
field in the DESCRIPTION file.monitor
constructor enables the development of new monitoring
backends in other packages (179f656, as part of #147).log_
activity has a new argument
level
which determines whether the message is printed depending on a global
log_level
defined in the simmer
constructor (#152).set_attribute
and set_global
gain a new argument to automatically
initialise new attributes (#157). Useful to update counters and indexes in a
single line, without initialisation boilerplate.-DRCPP_PROTECTED_EVAL
(Rcpp >= 0.12.17.3), which provides fast
evaluation of R expressions by leveraging the new stack unwinding protection
API (R >= 3.5.0).ostream
method (2b2f43e).rlang
and purrr
(#154).add_dataframe
enables the attachment of precomputed data,
in the form of a data frame, to a trajectory. It can be used instead of (or
along with) add_generator
. The most notable advantage over the latter is
that add_dataframe
is able to automatically set attributes and
prioritisation values per arrival based on columns of the provided data frame
(#140 closing #123).set_source
activity deprecates set_distribution()
. It works both for
generators and data sources (275a09c, as part of #140).simmer()
constructor gains a new argument mon
to provide different types of monitors.
By default, monitoring is performed in-memory, as usual. Additionally,
monitoring can be offloaded to disk through monitor_delim
and monitor_csv
,
which produce flat delimited files. But more importantly, the C++ interface
has been refactorised to enable the development of new monitoring backends
(#146 closing #119).until=Inf
for the run
method (3e6aae9, as part of #140).branch
and clone
now accept lists of trajectories, in the same way as
join
, so that there is no need to use do.call
(#142).continue
(present in seize
and branch
) is recycled if only
one value is provided but several sub-trajectories are defined (#143).timeout_from_attribute()
activity makes it easier to set a timeout based
on an attribute (#129).set_attribute()
, set_prioritization()
, set_capacity()
and
set_queue_size()
get a new argument mod
which, if set to "+"
or "*"
,
modifies the corresponding value instead of substituting it. This makes it
easier to increment, decrement or scale one of these values (#130).*_selected()
versions for the already available resource getters:
get_capacity()
, get_queue seize()
, get_server_count()
and
get_queue_count()
(#134).trap()
after a send()
(#135).create_trajectory()
and onestep()
(#117).get_mon_resources()
's data
argument. It was there for historical
reasons and probably nobody was using it (851d34b).set_attribute()
(and set_global()
by extension) can set multiple
attributes at once by providing vectors of keys
and values
(or functions
returning such keys
and/or values
). get_attribute()
(and get_global()
by extension) can retrieve multiple keys
(#122).stepn()
method deprecates onestep()
(e452975).ostream
after formatting (9ff11f8).set_capacity()
(#125).progress
callback in run()
(#103).get_name()
retrieves the arrival name.get_attribute()
retrieves an attribute by name. The old method of
retrieving them by providing a function with one argument is deprecated in
favour of get_attribute()
, and will be removed in version 3.7.x.get_prioritization()
retrieves the three prioritization values
(priority
, preemptible
, restart
) of the active arrival.set_global()
and get_global()
,
equivalent to set_attribute(global=TRUE)
and get_attribute(global=TRUE)
respectively.Rcpp::DataFrame
instead of Rcpp::List
(#104).make_resetable()
(c596f73).trap()
's handler cloning and associated test (#91).select()
's policy
also when resources
is a function (#92).rollback()
's default behaviour to times=Inf
, i.e., infinite loop (#95).timeout()
returns a missing value (#96 and #97).[<-
and [[<-
(#88).rep()
S3 method for trajectories (7fa515e).simmer.plot
package (on CRAN) already covers these features among others.Suggests
are not installed (e40e5b6).every
parameter to the from_to()
convenience function (9d68887).[
and [[
, for trajectories (1847898). Think
about trajectories as lists of activities and these operators will do (almost)
everything you expect. As a side effect, the generics head()
and tail()
automatically work with trajectories also as expected.length()
method to obtain the number of first-level activities in a
trajectory (f86375a). Useful in combination with the subsetting operators.create_trajectory()
has been deprecated in favor of trajectory()
(76c1317).plot_resource_usage()
, plot_resource_utilization()
,
plot_evolution_arrival_times()
and plot_attributes()
have been deprecated
and will be removed in the next release in order to minimise dependencies
(5b43f2b). We plan to release a new package on CRAN covering these features
and new ones.get_head()
,
get_tail()
, print_activity()
, get_next_activity()
, get_prev_activity()
(f86375a). These methods were only useful for development purposes and nobody
should be using them. And it was never a good idea to directly expose external
pointers.renege_if()
activity triggers reneging upon reception of a signal
broadcasted with send()
(#84).trap()
(bb2aa46).set_attribute()
(#82).set_queue_size()
with queue_size_strict=TRUE
: arrivals were
not being dropped (#83).set_capacity()
with a preemptive resource when the old value was
Inf
: arrivals were not being preempted (63beb2c).trap()
printed information.set_capacity()
and set_queue_size()
become activities (#77). Just like
seize()
and release()
, they have the associated set_capacity_selected()
and set_queue_size_selected()
for a joint use together with select()
.activate()
and deactivate()
activities allow an arrival to start or
stop a generator, respectively, from inside a trajectory (#80).set_trajectory()
and set_distribution()
activities allow an arrival
to install a new trajectory or distribution, respectively, in a generator
from inside a trajectory (#80).send()
, trap()
, untrap()
and wait()
can be used to send signals, wait
for signals, trap them and launch asynchronous handlers.log_()
activity simply prints messages for debugging purposes (eaa4554).seize()
and seize_selected()
(1c8c3bb).get_mon_arrivals(ongoing = TRUE)
(#73).as<>()
calls (ec4e51a).run(until)
runs the simulation exactly until until
, instead of until
the first event scheduled at a time >= until
(e7264f6).priority
, preemptible
, restart
) has been moved from
seize()
to add_generator()
(#69). This leads to a more natural
interpretation of prioritization values as attributes of arrivals from the
same generator, rather than attributes of a seize()
. Still, prioritization
values can be redefined dynamically from inside a trajectory with the new
activity set_prioritization()
.post.seize
and reject
sub-trajectories in seize()
and
seize_selected()
(#49). This feature allows us to fine-tune what happens to
an arrival if it cannot seize a resource: instead of getting dropped, it may
execute a given sub-trajectory.clone()
and synchronize()
activities (#71). clone()
implements the
workflow pattern in which an entity is processed in multiple parallel threads.
The user can define a different sub-trajectory for each clone. With
synchronize()
, multiple parallel clones converge and are synchronized: only
one continues (the first or the last to arrive), and the others are removed.batch()
and separate()
activities (#45). They can be used to implement
a rollercoaster process: batch()
collects a number of arrivals before they
can continue processing as a block, and separate()
splits a previousl
established batch.renege_in()
and renege_abort()
activities (#58). They can be used to
set or unset a timer after which the arrival will abandon.branch()
's option
returns 0
, the arrival skips the branch()
and continues to the next activity instead of throwing an index out of range
error (#70).every()
(#65) and branch()
's
deprecated argument merge
(#57).join()
activity to concatenate trajectories (#50).queue_size_strict
to add_resource()
to guarantee the queue
size limit with preemption (#59).select()
, seize_selected()
and release_selected()
activities (#52).leave()
activity (#63).end_time - start_time
. All
versions 3.2.x are affected by this bug.preemptible
in the documentation of seize()
and
force preemptible
to be equal or greater than priority
(#53).branch()
's merge
parameter name to continue
. The old name is
deprecated (#57).match.arg()
in multiple-choice arguments (#55).branch()
backwards linking and count (#56).release()
in two steps to deal properly with capacity changes at
the same point in time (#64).every()
is deprecated due to #65.capacity
and queue_size
can change over time following a user-defined
scheduling, which can be generated with the new function schedule()
.?peek
.plot_resource_usage()
(8da9b97).merge=TRUE
(#46).until=Inf
is allowed now (f47baa9).t=3, queue=2
meant that, until t=3
, the queue had
2 customers, and at t=3
the system changed (because of a new arrival or
a new departure). The idea was to keep the values and time vectors aligned
(see #28). But from this moment on, the resources are monitored after
changing the status of the system. This is more consistent with what a user
would expect, and more consistent with the behaviour of other related R
functions (e.g., see stepfun()
, from the stats
package). Wrapping up and
from now on, t=3, queue=2
means that some event happened at t=3
whose
immediate and subsequent result was a queue with 2 customers.preemptive=TRUE
. Arrivals in the server can be preempted on
a preempt_order="fifo"
or preempt_order="lifo"
basis. Each seize()
has
three basic properties:
priority
: already present in previous versions.preemptible
: another seize()
with a priority
value greater than this
may preempt the present seize()
.restart
: whether the current task (a timeout()
activity, for instance)
should be restarted if the arrival is preempted.show_activity()
and show_trajectory()
.every()
, to()
and from_to()
convenience functions (8e524cd).plot_resource_usage()
(6b034a7).testthat
(#41).branch()
activity now provides attributes to its option
function, as
the other activities (#42).plot_*()
functions (#44).get_mon_arrivals()
returned the start/activity/end times
per arrival for the whole trajectory. This behaviour remains, but additionally,
get_mon_arrivals(per_resource = TRUE)
returns these times per resource, so
that it is possible to retrieve queueing/system times per resource.get_mon_*()
functions accept a single simulation environment as well as a
list of environments representing several replications (5ee2725). A new column
(replication
) in the resulting data frame indicates the corresponding
replication number.set_attribute()
activity (#16).rollback()
activity (#17 and #22).self
visibly, instead of invisibly (#35).at()
and from()
convenience functions (29cccd2 and 7cfdd90).add_skip_event()
function to implement a more flexible and user-friendly
branching method.n
arrivals beforehand,
this release leverages the concept of generator of arrivals, which is faster
and more flexible. At the same time, the concept of trajectory as a chain of
activities is implemented entirely in C++ internally. Our tests show that
simmer is even faster than SimPy when it comes to simulate queueing networks.