Recently came across the brilliant Simmer package and been thoroughly enjoying myself. I'm a little green however (apologies if this is daft) and I'm struggling with custom priorities when there are multiple trajectories.
In this minimal working example have multiple trajectories being joined together. As a summary: an arrival has to go through traj_0, in which their priority for seizing a resource depends on how long they've been waiting. The arrivals then can go either on traj_1 or traj_2 depending on an exogenous 50/50% probability.
More specifically, in traj_0, an arrival increases in priority every time an arrival waits longer than t = 18. This is done as is recommended in the documentation here. However, I'm facing the following issues:
As soon as I implement the signal approach the simulation takes substantially longer. I imagine this is because it's having to re-evaluate priorities every single time a resource is about to be released. This makes it not very ideal as the number of arrivals or the simulation times increase. Am I doing something egregiously wrong / is there a work-around?
I think the recompute_priority signal is applying to every arrival on every trajectory, including patients on traj_1 and traj_2. This means that an arrival that has already cleared that pathway and is awaiting whether to go on traj_1 or 2 will have their priorities re-evaluated every single time resource_a will be released, which is not what we want. Is there any way to bind a signal to an arrival depending on which trajectory they're at? This would also allow me to implement a custom priority system for traj_1 and 2 as well.
Thanks!
#First trajectory: Every arrival should go through this
traj_0 <- simmer::trajectory() |>
simmer::set_attribute("arrival_time", function() simmer::now(sim)) |>
simmer::renege_if(
"recompute_priority",
out = simmer::trajectory() |>
# e.g., increase priority if wait_time > 3
simmer::set_prioritization(
function() {
if(simmer::now(sim) - simmer::get_attribute(sim, "arrival_time") > 18)
c(1, NA, NA) else
c(NA, NA, NA) }, mod="+") |>
simmer::rollback(2)) |>
simmer::timeout(rexp(1,1)) |>
simmer::seize('resource_a',1) |>
simmer::renege_abort() |>
#OUTCOME could be reached
simmer::log_('OUTCOME reached') |>
simmer::timeout(rexp(1,1)) |>
# Send signal that resource is being released
simmer::send("recompute_priority") |>
simmer::timeout(0) |>
simmer::release('resource_a',1)
#Trajectory branch 1
traj_1 <- simmer::trajectory() |>
simmer::timeout(rexp(1,1)) |>
simmer::seize('resource_b',1) |>
simmer::timeout(rexp(1,1)) |>
simmer::release('resource_b',1)
#Trajectory branch 2
traj_2 <- simmer::trajectory() |>
simmer::timeout(rexp(1,1)) |>
simmer::seize('resource_c',1) |>
simmer::timeout(rexp(1,1)) |>
simmer::release('resource_c',1)
#Main trajectory after traj_0, patient can either go traj_1 or 2.
main_traj <- simmer::trajectory() |>
#Probability is random 50/50%
simmer::branch(option = function() ifelse(runif(1)>0.5,1,2),
continue = c(T,T),
#branching trajectory
traj_0,
traj_1)
#Arrival behaviour
arrival_traj <- simmer::trajectory() |>
simmer::join(traj_0) |>
simmer::join(main_traj)
sim <- simmer::simmer()
sims <- sim |>
simmer::add_resource("resource_c", capacity = 1, queue_size=Inf, queue_size_strict=T, preemptive=TRUE)|>
simmer::add_resource("resource_b", capacity = 1, queue_size=Inf, queue_size_strict=T, preemptive=TRUE) |>
simmer::add_resource("resource_a", capacity = 1, queue_size=Inf, queue_size_strict=T, preemptive=TRUE) |>
simmer::add_generator("arrival", arrival_traj, function() rexp(1, 100), mon=2) |>
simmer::run(until=1000) |>
invisible()
Your simulation looks fine to me. The recompute_signal
does not apply to traj_1
or traj_2
, because these arrivals didn't execute the renege_if
statement, and therefore they are not "listening" for that signal. That said, yes, your simulation will be much slower as more and more events (signals, priority recomputation, etc.) need to be executed per unit of simulation time. This is the price to pay and there's no workaround.