Detecting when capacity exceeded after set_capacity

116 views
Skip to first unread message

Tom Lawton

unread,
Nov 23, 2018, 7:18:12 AM11/23/18
to simmer-devel

Hi,

 I'm pretty new to r-simmer - I've been simulating a hospital using my own code for now, and am trying to move to this package.

 When a hospital bed (resource) closes, the patient will have to be transferred elsewhere, but I'm not sure how best to detect that using r-simmer. By default it appears, if a resource is occupied when set_capacity reduces the resource availability, the actor is not "kicked off" and continues to occupy the resource as normal.

 Can anyone suggest a good way to model this within r-simmer? I've enclosed an example below based on one of the bank vignettes. Ideally I need some way of detecting that Customer0 and Customer1 need to find themselves some other resource until time 126.

Thanks for any advice!


library(simmer)
set.seed(1234)
bank <- simmer()
customer <-
  trajectory("Customer's path") %>%
  log_("Here I am") %>%
  set_attribute("start_time", function() {now(bank)}) %>%
  seize("counter") %>%
  log_(function() {paste("Waited: ", now(bank) - get_attribute(bank, "start_time"))}) %>%
  timeout(120) %>%
  release("counter") %>%
  log_(function() {paste("Finished: ", now(bank))})
bank <-
  simmer("bank") %>%
  add_resource("counter",capacity=2) %>%
  add_generator("Customer", customer, function() {c(0, rexp(4, 1/10), -1)}) %>% 
  add_generator("Blocker",blocker,at(26))
blocker <-
  trajectory("Blocker") %>%
  set_capacity(resource="counter",0) %>%
  log_("blocked") %>% 
  timeout(100) %>% 
  set_capacity(resource="counter",1) %>% 
  log_("unblocked")
bank %>% run(until = 400)

Tom Lawton

unread,
Nov 28, 2018, 9:37:50 AM11/28/18
to simmer-devel


On Friday, November 23, 2018 at 12:18:12 PM UTC, Tom Lawton wrote:
 When a hospital bed (resource) closes, the patient will have to be transferred elsewhere, but I'm not sure how best to detect that using r-simmer. By default it appears, if a resource is occupied when set_capacity reduces the resource availability, the actor is not "kicked off" and continues to occupy the resource as normal.

 Can anyone suggest a good way to model this within r-simmer? I've enclosed an example below based on one of the bank vignettes. Ideally I need some way of detecting that Customer0 and Customer1 need to find themselves some other resource until time 126.

It strikes me that an alternative way to do this would be to avoid using set_capacity, and instead leave capacity at whatever its maximum value will be, and seize resources (with pre-emption) to represent beds being closed etc. This should kick patients out as expected. I gather there's a queue_size_strict option to prevent those patients joining a queue (we have to put them somewhere!) - will they then just go down the "reject" trajectory as if they'd been denied the bed in the first place?

Iñaki Ucar

unread,
Nov 28, 2018, 10:32:22 AM11/28/18
to simmer...@googlegroups.com
On Wed, 28 Nov 2018 at 15:37, Tom Lawton <t...@t-lawton.org> wrote:
> On Friday, November 23, 2018 at 12:18:12 PM UTC, Tom Lawton wrote:
>>
>> When a hospital bed (resource) closes, the patient will have to be transferred elsewhere, but I'm not sure how best to detect that using r-simmer. By default it appears, if a resource is occupied when set_capacity reduces the resource availability, the actor is not "kicked off" and continues to occupy the resource as normal.
>>
>> Can anyone suggest a good way to model this within r-simmer? I've enclosed an example below based on one of the bank vignettes. Ideally I need some way of detecting that Customer0 and Customer1 need to find themselves some other resource until time 126.

For some reason I didn't receive your previous email.

> It strikes me that an alternative way to do this would be to avoid using set_capacity, and instead leave capacity at whatever its maximum value will be, and seize resources (with pre-emption) to represent beds being closed etc. This should kick patients out as expected. I gather there's a queue_size_strict option to prevent those patients joining a queue (we have to put them somewhere!) - will they then just go down the "reject" trajectory as if they'd been denied the bed in the first place?

There should be no difference between using set_capacity to close a
resource and seizing the whole resource with a higher priority. I.e.,
if preemption is enabled and queue_size_strict is set to true,
everything is rejected.

The problem (for your use case) is that the "reject" trajectory is not
honored in such cases, as it would be if the resource is denied in the
first place. But now that you put it in that way, maybe this is
confusing, and maybe the "reject" trajectory should be followed by
rejections due to preemption too. I have to think about it.

Meanwhile, you can use signals and you don't need preemption:

library(simmer)

kicked <- trajectory() %>%
log_("I've been kicked!")

patient <- trajectory() %>%
seize("bed") %>%
trap("bed closed", handler=kicked, interruptible=FALSE) %>%
timeout(10) %>%
release("bed")

blocker <- trajectory() %>%
set_capacity("bed", 0) %>%
send("bed closed")

env <- simmer() %>%
add_resource("bed") %>%
add_generator("patient", patient, at(0)) %>%
add_generator("blocker", blocker, at(5)) %>%
run()
#> 5: patient0: I've been kicked!

Iñaki

Tom Lawton

unread,
Nov 28, 2018, 10:45:30 AM11/28/18
to simmer-devel


On Wednesday, November 28, 2018 at 3:32:22 PM UTC, Iñaki Úcar wrote:
On Wed, 28 Nov 2018 at 15:37, Tom Lawton <t...@t-lawton.org> wrote:
> On Friday, November 23, 2018 at 12:18:12 PM UTC, Tom Lawton wrote:

The problem (for your use case) is that the "reject" trajectory is not
honored in such cases, as it would be if the resource is denied in the
first place. But now that you put it in that way, maybe this is
confusing, and maybe the "reject" trajectory should be followed by
rejections due to preemption too. I have to think about it.

 Thanks for the quick response - I've just been looking through the C code and realised the same! In my original "blocker" example the two pre-empted customers just vanish and never complete. It would certainly make sense to me to have some way of defining a trajectory for preemption. Though presumably some of the "success" trajectory gets followed too. A new set of functions working like the way "renege" works might cover all bases?


Meanwhile, you can use signals and you don't need preemption:

I looked at using signals, but it seemed complex in terms of situations where I have multiple wards and am closing a subset of beds on each ward (ie not all patients need kicking!) It did strike me that it might be possible to assign all patients already in beds to have a very high priority, and each time a bed closure occurs to kick them all out using a signal, and let them scramble to get back in!

tom

Iñaki Ucar

unread,
Nov 28, 2018, 11:11:18 AM11/28/18
to simmer...@googlegroups.com
On Wed, 28 Nov 2018 at 16:45, Tom Lawton <t...@t-lawton.org> wrote:
> On Wednesday, November 28, 2018 at 3:32:22 PM UTC, Iñaki Úcar wrote:
>>
>> On Wed, 28 Nov 2018 at 15:37, Tom Lawton <t...@t-lawton.org> wrote:
>> > On Friday, November 23, 2018 at 12:18:12 PM UTC, Tom Lawton wrote:
>>
>> The problem (for your use case) is that the "reject" trajectory is not
>> honored in such cases, as it would be if the resource is denied in the
>> first place. But now that you put it in that way, maybe this is
>> confusing, and maybe the "reject" trajectory should be followed by
>> rejections due to preemption too. I have to think about it.
>
>
> Thanks for the quick response - I've just been looking through the C code and realised the same! In my original "blocker" example the two pre-empted customers just vanish and never complete. It would certainly make sense to me to have some way of defining a trajectory for preemption. Though presumably some of the "success" trajectory gets followed too. A new set of functions working like the way "renege" works might cover all bases?

A new set of functions would be overkilling. I was thinking about
letting them retry the "seize" activity.

>> Meanwhile, you can use signals and you don't need preemption:
>
>
> I looked at using signals, but it seemed complex in terms of situations where I have multiple wards and am closing a subset of beds on each ward (ie not all patients need kicking!) It did strike me that it might be possible to assign all patients already in beds to have a very high priority, and each time a bed closure occurs to kick them all out using a signal, and let them scramble to get back in!

Yeap, a "rollback" inside the signal handler would do the trick.

Iñaki

Iñaki Ucar

unread,
Nov 28, 2018, 12:03:27 PM11/28/18
to simmer...@googlegroups.com
On Wed, 28 Nov 2018 at 17:11, Iñaki Ucar <iu...@fedoraproject.org> wrote:
>
> On Wed, 28 Nov 2018 at 16:45, Tom Lawton <t...@t-lawton.org> wrote:
> > On Wednesday, November 28, 2018 at 3:32:22 PM UTC, Iñaki Úcar wrote:
> >>
> >> On Wed, 28 Nov 2018 at 15:37, Tom Lawton <t...@t-lawton.org> wrote:
> >> > On Friday, November 23, 2018 at 12:18:12 PM UTC, Tom Lawton wrote:
> >>
> >> The problem (for your use case) is that the "reject" trajectory is not
> >> honored in such cases, as it would be if the resource is denied in the
> >> first place. But now that you put it in that way, maybe this is
> >> confusing, and maybe the "reject" trajectory should be followed by
> >> rejections due to preemption too. I have to think about it.
> >
> >
> > Thanks for the quick response - I've just been looking through the C code and realised the same! In my original "blocker" example the two pre-empted customers just vanish and never complete. It would certainly make sense to me to have some way of defining a trajectory for preemption. Though presumably some of the "success" trajectory gets followed too. A new set of functions working like the way "renege" works might cover all bases?
>
> A new set of functions would be overkilling. I was thinking about
> letting them retry the "seize" activity.

After some thought, I don't think we should change the current
behaviour. Once the seize is executed, either the arrival is enqueued
or served, the execution advances to the next activity. This means
that, in general, we can't go back, because the seize activity may not
be reachable anymore (for example, if it is inside a branch and the
next activity is outside that branch). Furthermore, if we have
something like this:

trajectory() %>%
seize("bed", continue=FALSE, reject=some_trajectory) %>%
timeout(12) %>%
... %>%
timeout(5) %>%
release("bed")

And preemption (and rejection) occurs in the last timeout, I don't
think that anybody should expect that the "reject" trajectory
n-activities behind should be honored.

Instead, there are signaling mechanisms (trap > send, or renege_if >
send) that allows us to implement your use case and redirect the
arrivals in a controlled way (even to retry seizing with different
priorities, as you said).

Iñaki

Tom Lawton

unread,
Nov 28, 2018, 12:40:28 PM11/28/18
to simmer-devel

On Wednesday, November 28, 2018 at 5:03:27 PM UTC, Iñaki Úcar wrote:
On Wed, 28 Nov 2018 at 17:11, Iñaki Ucar <iu...@fedoraproject.org> wrote:
> On Wed, 28 Nov 2018 at 16:45, Tom Lawton <t...@t-lawton.org> wrote:
> > On Wednesday, November 28, 2018 at 3:32:22 PM UTC, Iñaki Úcar wrote:
> >> On Wed, 28 Nov 2018 at 15:37, Tom Lawton <t...@t-lawton.org> wrote:
> >> > On Friday, November 23, 2018 at 12:18:12 PM UTC, Tom Lawton wrote:
 
 I don't
think that anybody should expect that the "reject" trajectory
n-activities behind should be honored.

I agree - that's why I was wondering about something similar to the renege functions to let me specifically set a reject trajectory. However, if it can be done reasonably with signals I'm up for trying that! 
 
Instead, there are signaling mechanisms (trap > send, or renege_if >
send) that allows us to implement your use case and redirect the
arrivals in a controlled way (even to retry seizing with different
priorities, as you said).

 Do you have any recommendations on best practice here? I'm trying to avoid using my "everyone releases and seizes the bed on a signal" because it seems wrong (plus, what's the effect of trying to release a resource that's been pre-empted off you?) I don't really want to risk my patients sitting in queues as they always need to occupy a resource, but if I use a strict zero queue size then patient trajectories just get terminated when they have their bed closed. I need some way of allowing the pre-empted patients to head off down a new trajectory (where they select a new bed to move into, representing a transfer) whilst all the ones still in beds get left alone as much as is possible. 

tom

Iñaki Ucar

unread,
Nov 28, 2018, 3:28:34 PM11/28/18
to simmer...@googlegroups.com
Did you take a look at the C++ core? When an arrival is rejected, the
"terminate" method is called with the "finished" parameter set to
false. What we could do would be to implement a new activity to set a
global rejection trajectory. In this way, whenever terminate(false) is
called, the arrival would follow that trajectory. I think this is the
cleanest solution, because the resource logic doesn't need to be
touched.

It would work as follows:

rejected <- trajectory() %>%
log_("I've been kicked")

patient <- trajectory() %>%
new_activity(rejected) %>%
seize("bed") %>%
timeout(10) %>%
release("bed")

What do you think? We would need a good name for the new activity though...

Iñaki

Tom Lawton

unread,
Nov 28, 2018, 4:18:59 PM11/28/18
to simmer-devel
This looks perfect for my needs - definitely in support.
In terms of names for the activity - preempted()  kicked() terminated() if_preempted()  if_kicked()  if_terminated()  preempted_trajectory()  kicked_trajectory()  terminated_trajectory()  ?

tom

Iñaki Úcar

unread,
Nov 29, 2018, 4:28:53 AM11/29/18
to simmer...@googlegroups.com
Thanks. A verb would be better, like other activities, and something more general. Moreover, "terminated" alludes to the internal API, which doesn't need to be known by users. And "kicked" and "preempted" is very resource specific. There is another case that should take that trajectory: after the "leave" activity. And in the future may be more cases, so I would pick a more generic name.

I was thinking that this is like a "catch" block in a try-catch, but maybe "catch" is too generic? What about "handle_exception", "handle_rejection"...? Any more ideas in this line?

Iñaki

Tom Lawton

unread,
Nov 29, 2018, 5:41:30 AM11/29/18
to simmer-devel
Thanks for looking into this!

I have to say I quite like "catch", though is there any risk people might confuse it for a more generic (ie R-based) error handler? "handle_exception" makes sense to me too. "handle_rejection" seems confusing as we can already handle some forms of rejection by passing a trajectory to seize()

Along similar lines - handle_exit  handle_unfinished  cleanup  finalise (which would have to be even more generic and allow people to specify a trajectory even if 'finished')

Whilst I agree with you about it being generic, I think it would be important to either be able to set flags to show what events you're interested in, or specify trajectories for different events, or have variables available to show what's happened (or a combination). Though I guess it depends on how many routes into it there are (for now it seems to be leave() and being pre-empted without a queue to fall into? And for leave() we can set an attribute immediately prior)

tom

Iñaki Ucar

unread,
Nov 29, 2018, 6:01:07 AM11/29/18
to simmer...@googlegroups.com
On Thu, 29 Nov 2018 at 11:41, Tom Lawton <t...@t-lawton.org> wrote:
>
> I have to say I quite like "catch", though is there any risk people might confuse it for a more generic (ie R-based) error handler? "handle_exception" makes sense to me too. "handle_rejection" seems confusing as we can already handle some forms of rejection by passing a trajectory to seize()

Agree.

> Along similar lines - handle_exit handle_unfinished cleanup finalise (which would have to be even more generic and allow people to specify a trajectory even if 'finished')

I particularly like handle_unfinished. Unless we come up with a
brilliant idea, I think this one is the best.

> Whilst I agree with you about it being generic, I think it would be important to either be able to set flags to show what events you're interested in, or specify trajectories for different events, or have variables available to show what's happened (or a combination). Though I guess it depends on how many routes into it there are (for now it seems to be leave() and being pre-empted without a queue to fall into? And for leave() we can set an attribute immediately prior)

There are already flags to show what happened. If the arrival was
preempted or dropped from a queue, get_mon_arrivals(per_resource=TRUE)
will show the seize event, and from the activity time you know whether
it was a preemption or a queue drop. If there is no info in such a
table, then the arrival left the trajectory using "leave".

I've opened a new issue on GitHub to track this:

https://github.com/r-simmer/simmer/issues/177

If you have any further comments or suggestions, please put them
there. And thanks for your feedback!

Iñaki
Reply all
Reply to author
Forward
0 new messages