rperformancesimulationschedulingdiscrete-mathematics

R Simmer: Custom priority with multiple trajectories


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:

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()

Solution

  • 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.