Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select/seize, select/release multiple resources #17

Open
whipson opened this issue Jun 7, 2022 · 8 comments
Open

Select/seize, select/release multiple resources #17

whipson opened this issue Jun 7, 2022 · 8 comments

Comments

@whipson
Copy link

whipson commented Jun 7, 2022

I've been using simmer for a few months and love it. There's one consistent challenge I encounter though and it has to do with monitoring individual resource entities within a group.

Let's say you're simulating a hospital with 3 doctors and you want to track the activity of each doctor individually. I know it's possible to provide a vector of names to add_resource. But this creates some challenges. Sometimes I want to allow an arrival to take one or more of a resource type (in this example, take one or more doctors depending on some attribute). In this case, I can't simply use select and seize_selected, I now have to rollback and select/seize again as needed. It'd be much easier if I could use seize("doctor", amount = \() get_attribute(env, "num_to_seize")) while still being able to know exactly which doctors were seized in get_mon_arrivals/get_mon_resources using some kind of id.

I've usually been able to create work-arounds for this using rollbacks over select/seize, but it'd be great if there was an easier way.

Love the package and looking forward to your response!

@Enchufa2
Copy link
Member

Enchufa2 commented Jun 24, 2022

Can you provide a small example that illustrates the problem and that we can work on to see alternatives, please?

@whipson
Copy link
Author

whipson commented Jun 24, 2022

Sure. In essence, what I want to do is include in seize and release a vector of resources. I don't want to use select because the resource is part of the same resource type. In the example below, I want to refer to doctors as a single resource but have different names for each doctor. I realize it may be too much to ask, but I figured I'd try anyway.

Here is what I want to be able to do:

library(simmer)

arrivals_dat <- data.frame(
  time = c(0, 3, 6, 9, 10),
  num_to_seize = c(1, 0, 2, 1, 2) # the number of doctors needed for each arrival
)

# I create distinct 'doctors' using a numeric suffix
doctors <- paste0("doctor", 1:2)

env <- simmer()

traj <- trajectory() |> 
 # A big part of the challenge is that I want to seize a variable number of doctors dependent on arrival attribute
  seize(doctors, amount = \() get_attribute(env, "num_to_seize")) |> 
  timeout(4) |> 
  release_all(doctors)

env |> 
  add_resource(doctors, capacity = 1) |> 
  add_dataframe("arrival", traj, arrivals_dat, col_attributes = "num_to_seize")

env |> 
  run()

The trajectory fails with Error: Expecting a single string value: [type=character; extent=2].. I understand that seize takes a single string representing a resource.

Here is my workaround with the trajectory:

traj <- trajectory() |> 
  # Check to see if the arrival needs more doctors than it currently has seized
  branch(\() get_attribute(env, "num_to_seize") > sum(get_seized(env, doctors) == 1),
         trajectory() |> 
           select(doctors) |> 
           seize_selected(amount = 1) |> 
           rollback(3),
         continue = TRUE) |> 
  timeout(4) |> 
  # Check to see if arrival has any doctors seized and release them
  branch(\() any(get_seized(env, doctors) == 1),
         trajectory() |> 
           select(\() {
             # Select the first seized resource
             doctors[get_seized(env, doctors) == 1][[1]]
           }) |> 
           release_selected() |> 
           rollback(3),
         continue = TRUE)

When using this trajectory, I get the desired result as can be seen when I print get_mon_arrivals:

> env |> 
   get_mon_arrivals(per_resource = TRUE)

      name start_time end_time activity_time resource replication
1 arrival0          0        4             4  doctor1           1
2 arrival2          9       13             4  doctor1           1
3 arrival2          9       13             4  doctor2           1
4 arrival3         18       22             4  doctor1           1
5 arrival4         28       32             4  doctor1           1
6 arrival4         28       32             4  doctor2           1

It's manageable for this small example, but in practice this gets very complicated to manage.

@Enchufa2
Copy link
Member

Enchufa2 commented Jun 24, 2022

The thing is that every resource has its own queue. So this depends on whether you want several doctors with a single queue or whether each doctor must have their own queue.

If you want the former (one resource; several servers, single queue), then tracking which server was assigned is not possible right now because:

  1. By design, resource servers are indistinguishable. Capacity is not meant to represent individuals, you are just taking a share of that resource.
  2. A resource is made up of a server and a queue. If we implement Split resource into queue and server simmer#121 at some point, it would be possible to decouple them and model more complicated architectures.

If you want the latter (several resources; several servers, several queues), seizing several different resources from a single activity would be problematic. Activities are the unit of action. When you call seize, you enter a resource (either in the queue or in the server, or you are dropped) and the action is finished. With several resources in a single seize, the arrival could be served in some resources, queued in others and/or dropped in others. It's even more problematic if you want to share the amount, instead of applying the same amount (or a vector of amounts) across resources, which I think would be the sensible thing to do.

So from the description of what you want to achieve, I think that your second example (or some variation) is definitely the way to go. It gets complicated, yes. But then a generalisation of this pattern could be encapsulated into a brick (see https://github.com/r-simmer/simmer.bricks) to make it more manageable.

@Enchufa2
Copy link
Member

In other words, we could create activities that do many things (even to the point to encapsulate whole simulation models), but we try to keep things simple. I'm willing to complicate the design to add some functionality if it's not achievable in any way. If there's some pattern of existing activities that solves the problem, then simmer.bricks is the place for that (and PRs are very welcome ;-) ).

@whipson
Copy link
Author

whipson commented Jun 24, 2022

Thanks @Enchufa2 I figured as much. I may try and abstract the problem into a brick as you suggested - I've already done so to some extent but in my current use case it's already started to get a bit unwieldy.

@Enchufa2 Enchufa2 transferred this issue from r-simmer/simmer Jun 24, 2022
@Enchufa2
Copy link
Member

Issue transferred then. :)

@whipson whipson changed the title Tracking individual resources using an id Select/seize, select/release multiple resources Jun 24, 2022
@whipson
Copy link
Author

whipson commented Jun 24, 2022

I renamed the issue to better reflect what I'd like to accomplish.

@Enchufa2
Copy link
Member

Enchufa2 commented Nov 7, 2022

Related: https://groups.google.com/g/simmer-devel/c/7M52Ivj4p6g/m/luFL71bWAgAJ. We definitely could write down this pattern in a brick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants