Search code examples
revent-simulation

R simmer resource not dropping arrival when capacity --> 0


In my simulation, certain resources are on a capacity schedule, which alternates between 0 and 1 based on the time of day. If a resource cannot complete its task by the time its capacity goes to zero, the arrival should be dropped, wait a short period, and rollback to try to select an available resource. If no resource is available, the arrival goes through a timeout --> rollback loop until a resource becomes available.

The relevant code is as follows:

# Use a reject trajectory used to go back and select a new resource if the
# current resource runs out of time (i.e., its shift ends)
... |> 
simmer::seize_selected(
  continue = TRUE,
  reject = simmer::trajectory('dropped') |>
    simmer::timeout(\() runif(1, min = 0.07, max = 0.1)) |>
    simmer::rollback(target = 'select_resource')
  ) |>

# Clear the queue of the selected resource when its capacity is zero
simmer::renege_if(
  signal = 'clear_queue'
  ) |>
simmer::send(
  signals =  \() {
    cap <- simmer::get_capacity_selected(env)

    res_name <- simmer::get_selected(env)
    now <- simmer::now(env)

    queue_count <- simmer::get_queue_count_selected(env)
    if (res_name == 'Operator_Bill' & now > 41 & queue_count > 0) {
      browser()
    }

    if (cap == 0) {
      'clear_queue'
      } else {
        ''
      }
    }
  ) |> ...

The debug condition is never triggered for some reason, but on inspecting mon_resources it shows that arrivals are being served/in queue when capacity == 0. What would be the correct code to handle this situation?

mon_resources

usage_plot


Solution

  • Here's a simplified example of your question:

    library(simmer)
    
    t <- trajectory() %>%
      seize("res") %>%
      timeout(Inf)
    
    simmer() %>%
      add_resource("res", capacity=schedule(c(0, 5), c(1, 0))) %>%
      add_generator("dummy", t, at(0)) %>%
      run(10)
    #> simmer environment: anonymous | now: 10 | next: Inf
    #> { Monitor: in memory }
    #> { Resource: res | monitored: TRUE | server status: 1(0) | queue status: 0(Inf) }
    #> { Source: dummy | monitored: 1 | n_generated: 1 }
    

    In the example above, an arrival seizes a resource and stays there. The resource "closes" at t=5 thanks to the specified schedule. But then in the simulation status at t=10 we can see that the arrival is still in the server. And this is what you are trying to achieve:

    t <- trajectory() %>%
      handle_unfinished(trajectory() %>% log_("dropped!")) %>%
      seize("res") %>%
      timeout(Inf)
    
    simmer() %>%
      add_resource("res", capacity=schedule(c(0, 5), c(1, 0)),
                   queue_size=0, queue_size_strict=TRUE, preemptive=TRUE) %>%
      add_generator("dummy", t, at(0)) %>%
      run(10)
    #> 5: dummy0: dropped!
    #> simmer environment: anonymous | now: 5 | next: 
    #> { Monitor: in memory }
    #> { Resource: res | monitored: TRUE | server status: 0(0) | queue status: 0(0) }
    #> { Source: dummy | monitored: 1 | n_generated: 1 }
    

    And now the arrival is dropped. Several things to note:

    • By default, resources are not preemptive, which means that, even if the capacity drops, the arrivals that managed to seize the resource will still be there. So the first thing we need is to specify the preemptive=TRUE flag.
    • With this flag, the arrival is dropped from the server, but it will stay in the queue. Therefore, we need to specify queue_size=0, but also queue_size_strict=TRUE, because preempted arrivals are by default allowed to exceed the queue size. And now the arrival is effectively dropped from the resource.
    • Finally, if you want to handle the dropped arrival, seize's reject argument won't work, because the arrival was not rejected: it was dropped after accessing the resource. The last piece of the puzzle is the handle_unfinished() activity, which was designed to handle these special cases.