1

I am relatively new to Python and very new to Simpy. I am trying to build a model of a hospital system that:

  • Has roughly 20 resources (units) where each resource has a capacity of 5 to 50 (beds)
  • Not all units can serve the same patients. They have specialties.
  • When a patient needs a bed, the hospital requests 1 bed from roughly 5 of the 20 units.

So, what I want to do is make a request against multiple Unit resources and only capture 1 bed from 1 available Unit. After many iterations, I think I have found a way of doing this but my approach feels overly complicated.

Below I am showing code using the conditional AnyOf of Simpy. The way AnyOf works is if more than 1 resource has the availability, then more than 1 resource will be captured. So, after the AnyOf request, I release any extra captured beds and cancel any requests still pending.

Is there an easier approach?

from dataclasses import dataclass
import simpy
from simpy.events import AnyOf, Event
import random


@dataclass
class Unit():
    env: simpy.Environment()
    identifier: str
    capacity: int = 1

    def __post_init__(self):
        self.beds: simpy.Resource = simpy.Resource(env, self.capacity)


def make_request(env, units: list[Unit]):

    # create a list of simpy request events for unit 1, 3 and 5
    # purpose is to put in a conditional request
    any_of_request: list[Event] = []
    request_to_unit_dictionary = {}

    random_pool_size = random.randint(1, 5)

    for x in range(random_pool_size):

        random_unit = random.randint(0, 9)
        unit = units[random_unit]
        res_request = unit.beds.request()
        request_to_unit_dictionary[res_request] = unit
        any_of_request.append(res_request)

    get_one_bed_request = AnyOf(env, any_of_request)

    captured_units = yield get_one_bed_request
    # how do i determine what unit was captured by the request?
    # it is possible that both resources are avaliable
    # and both request get filled
    captured_requests = list(captured_units.keys())
    captured_request = captured_requests[0]

    captured_unit: Unit = request_to_unit_dictionary[captured_request]
    print(captured_unit)
    # if I understand correctly, if I only want 1 request then
    # release any "extra" captures
    for r in captured_requests:
        if r != captured_request:
            r.resource.release(r)

    # cancel any of the request not captured.
    for r in any_of_request:
        if r not in captured_requests:
            r.cancel()


env = simpy.Environment()
# create 10 units, each with 1 capacity for this example
units: list[Unit] = []
for x in range(1, 10):
    units.append(Unit(identifier=f'unit{x}', env=env, capacity=1))

env.process(make_request(env, units))
env.process(make_request(env, units))
env.process(make_request(env, units))
env.process(make_request(env, units))
env.run()

Thanks, Dan

DapperDanh
  • 555
  • 2
  • 5
  • 17
  • 1
    This is pretty good. You even remember to cancel the requests that did not fire. – Michael Jan 27 '22 at 01:42
  • 1
    Another way to do it is to first check each unit for available resources. If you find a unit with a available resource you just need to make one request. However if none of the resources have any resources available you are back to dong what you did in your example. – Michael Jan 27 '22 at 01:56
  • 1
    one last alternative is to use a filtered store instead of a resource pool. you would put all your resources into one filtered store. Each resource would have a unit attribute. Each request would include a list of units to filter on. The advantage is you only need one request. The disadvantage is it is harder to collect stats on each unit. – Michael Jan 27 '22 at 16:57
  • Michael, thanks so much for the feedback. I am looking into filtered stores and they look promising. You mention it is harder to collect stats on each unit. can you elaborate why it would be harder? I am thinking of pros/cons of shared resources vs stores and it appears to me that shared resources can have infinite capacity whereas, filtered stores cannot. To model inf capacity with filter stores, I would imagine you would have to load up the store with a very large number of items? – DapperDanh Jan 28 '22 at 03:19
  • when you break out your resources into units where each unit is its own resource pool, each unit tracks how many resources are available and how long the queue is for that unit. when all your resources are in one store you will have only one queue, so you will have to track which units each request is requesting. Also the store will give you how many resources are in the store, but you will have to track how many of each unit type yourself. One way would be to just iterate over the resources in the store and count how many of each type you have, but I'm not sure how efficient that would be. – Michael Jan 28 '22 at 05:17
  • I think the default capacity for stores is inf. When you create a store it starts out empty and as part of your boot up you need to create your objects/resources and add them to the store. With a store you can add any object to it you want until you reach the capacity , then the add will queue. You can think of stores more like queues then resource pools. Stores are often used to model warehouses. – Michael Jan 28 '22 at 05:20
  • @Michael: Thanks for your support. I continue to enhance my proof of concept and hit another snag which I posted here. https://stackoverflow.com/questions/70916742/python-full-execute-one-generator-and-based-upon-result-fully-execute-another-y – DapperDanh Jan 30 '22 at 16:43

0 Answers0