I have an endless loop problem in Simpy simulation.
My scenario is:
After finishing triage, a patient wait until getting empty bed.
However, if there is no empty bed, the patient should wait until an empty bed is available.
When a patient uses a bed and leaves the hospital, the bed turns into a dirty bed.
The dirty bed turns into an empty bed when the cleaner cleans it.
Bed cleaning work start after getting clean request.
I used "Store" for managing beds
I think I have an infinite loop when there is no empty bed and no dirty bed.
I think...
Add a queue (After triage)
From the queue, assign a bed based on the FIFO.
If we don't have empty or dirty bed, we should wait until a dirty bed is available.
But, I don't know how to implement this idea.
Please help me to solve this problem.
import simpy
import random
class Pre_Define:
warmup_period = 1440
sim_duration = 14400
number_of_runs = 3
class Patients:
def __init__(self, p_id):
self.id = p_id
self.bed_name = ""
self.admission_decision = ""
def admin_decision(self):
admin_decision_prob = random.uniform(0, 1)
if admin_decision_prob <= 0.7:
self.admission_decision = "DIS"
class Model:
def __init__(self, run_number):
self.env = simpy.Environment()
self.pt_counter = 0
self.tg = simpy.Resource(self.env, capacity = 4)
self.physician = simpy.Resource(self.env, capacity = 4)
self.bed_clean = simpy.Store(self.env, capacity = 77)
self.bed_dirty = simpy.Store(self.env, capacity = 77)
self.IU_bed = simpy.Resource(self.env, capacity = 50)
self.bed_cleaner = simpy.Resource(self.env, capacity = 2)
self.run_number = run_number
def generate_beds(self):
for i in range(77):
yield self.env.timeout(0)
yield self.bed_clean.put(f'bed{i}')
print(self.bed_clean.items)
def generate_pt_arrivals(self):
while True:
self.pt_counter += 1
pt = Patients(self.pt_counter)
yield self.env.timeout(1/7)
self.env.process(self.Process(pt))
def Process(self, Patients):
with self.tg.request() as req:
yield req
triage_service_time = random.expovariate(1.0/18)
yield self.env.timeout(triage_service_time)
if self.bed_clean.items != []:
get_empty_bed_name = yield self.bed_clean.get()
Patients.bed_name = get_empty_bed_name
elif self.bed_dirty.items != []:
get_dirty_bed_name = yield self.bed_dirty.get()
with self.bed_cleaner.request() as req:
yield req
yield self.env.timeout(50)
else:
print("NO BED, Should Wait!!")
no_bed = True
while no_bed:
#print("Waiting dirty bed")
if self.bed_dirty.items != []:
get_dirty_bed_name = yield self.bed_dirty.get()
print("Find dirty bed!")
with self.bed_cleaner.request() as req:
yield req
yield self.env.timeout(30)
Patients.bed_name = get_dirty_bed_name
no_bed = False
with self.physician.request() as req:
yield req
yield self.env.timeout(10)
Patients.admin_decision()
if Patients.admission_decision == "DIS":
with self.IU_bed.request() as req:
yield req
yield self.env.timeout(600)
get_dirty_bed_name = Patients.bed_name
yield self.bed_dirty.put(get_dirty_bed_name)
else:
get_dirty_bed_name = Patients.bed_name
yield self.bed_dirty.put(get_dirty_bed_name)
def run(self):
self.env.process(self.generate_pt_arrivals())
self.env.process(self.generate_beds())
self.env.run(until = Pre_Define.warmup_period + Pre_Define.sim_duration)
for run in range(Pre_Define.number_of_runs):
run_model = Model(run)
run_model.run()
print()
So you got the right idea where you use two Stores to track dirty and clean beds. The trick is to request both a clean and a dirty bed at the same time, and discard the request you do not use.
So the big changes I made was to request both a clean bed and a dirty bed and used env.any_of() to get me the first filled request. Note that both requests could get filled. Since I made two requests, that means I need to either cancel the request I do not use, or if filled, return the unused bed back to its queue. The other thing I did was to make a separate process for the cleaners. This means that the both the patients and the cleaners will be competing for dirty beds.
"""
Quick sim model of beds in a hosptial triage
beds have two states, clean and dirty
When a patient arrives they are assigned to a empty bed
If no beds are avail then the patient wait for first empty bed
If there is both a clean bed and a empty bed, the clean bed will be assigned
When the patient leaves triage the bed state is dirty.
empty dirty beads are queued to be clean, after cleaning bed state is clean
After being triaged, a patient is either admited or discharged.
If the patient is admitted, then they wait for a IU bed befor the triage bed is empty
Programmer: Michael R. Gibbs
"""
import simpy
import random
TRIAGE_TIME = 30
BED_CLEAN_TIME = 20
class Patient():
"""
has id to track patient progress
and bed when assigned to a bed
"""
next_id = 1
#classmethod
def get_next_id(cls):
id = cls.next_id
cls.next_id += 1
return id
def __init__(self):
self.id = self.get_next_id()
self.bed = None
class Bed():
"""
has id to track patient progress
and bed when assigned to a bed
"""
next_id = 1
#classmethod
def get_next_id(cls):
id = cls.next_id
cls.next_id += 1
return id
def __init__(self):
self.id = self.get_next_id()
self.bed_state = 'Clean'
def clean_beds(env, dirty_bed_q, clean_bed_q):
"""
Sim process for cleaning dirty beds from
the dirty queue, and putting the clean
beds into clean queue
A instance shoud be started for each cleaner
"""
while True:
bed = yield dirty_bed_q.get()
print(f'{env.now:.2f} bed {bed.id} is being cleaned')
# clean
yield env.timeout(BED_CLEAN_TIME)
bed.bed_state = "Clean"
clean_bed_q.put(bed)
print(f'{env.now:.2f} bed {bed.id} is clean')
def triage(env, pat, clean_bed_q, dirty_bed_q, ui_bed_q):
"""
models the patients life cycle in triage
stepss are:
get bed (clean perfered)
get triaged
leave
if addmited leaving is blocked until a ui bed is found
"""
# get bed
clean_req = clean_bed_q.get()
dirty_req = dirty_bed_q.get()
print(f'{env.now:.2f} patient {pat.id} has arrived')
fired = yield env.any_of([clean_req, dirty_req])
# see if we got a clean or dirty or both
if clean_req in fired:
# got a clean bead
pat.bed = fired[clean_req]
# need to deal with the dirty req
if dirty_req in fired:
# got two beds but dirty back
dirty_bed_q.put(fired[dirty_req])
else:
# stil need to cancel req
dirty_req.cancel()
else:
# we have a dirty bed
pat.bed = fired[dirty_req]
# need to deal with the dirty req
if clean_req in fired:
# got two beds but dirty back
clean_bed_q.put(fired[clean_req])
else:
# stil need to cancel req
clean_req.cancel()
print(f'{env.now:.2f} {pat.bed.bed_state} bed {pat.bed.id} is occupied and dirty with patient {pat.id}')
pat.bed.bed_state = 'Dirty'
# triage
yield env.timeout(TRIAGE_TIME)
admit = (random.uniform(0,1) < 0.7)
if admit:
# need to get a IU bed before
# patient gives up triage bed
print(f'{env.now:.2f} patient {pat.id} is being admitted')
ui_req = ui_bed_q.request()
yield ui_req
print(f'{env.now:.2f} patient {pat.id} got iu bed')
# patient leaves triage
# give up dirty bed
dirty_bed_q.put(pat.bed)
print(f'{env.now:.2f} bed {pat.bed.id} is empty and patient {pat.id} has left')
def gen_pats(env, clean_bed_q, dirty_bed_q, iu_bed_q):
"""
creates the arriving patients
"""
while True:
yield env.timeout(random.uniform(1,8))
pat = Patient()
# not each patient gets their own process
# so if one patient blocks while waiting for a iu bed, it does
# not block the other patients
env.process(triage(env, pat, clean_bed_q, dirty_bed_q, iu_bed_q))
def model():
env = simpy.Environment()
# make queues
clean_bed_q = simpy.Store(env)
dirty_bed_q = simpy.Store(env)
iu_bed_q = simpy.Resource(env,capacity=5)
clean_bed_q.items = [Bed() for _ in range(10)]
# start generating patients
env.process(gen_pats(env, clean_bed_q, dirty_bed_q, iu_bed_q))
# star up cleaners
for _ in range(2):
env.process(clean_beds(env, dirty_bed_q, clean_bed_q))
env.run(until=100)
print('simulation finish')
model()
Related
I want to use other def values.
For example, I added a 'pt' in the 'clean_beds_process' definition and add 'Patients' in the 'run' definition.
I want to patient information when the 'clean_beds_process' function is called.
However, this makes this error 'AttributeError: type object 'Patients' has no attribute 'id''
I don't know why this happen.
Maybe I have something wrong understanding of mechanism of simpy.
Please let me know how can I use a patient information when 'clean_beds_process' function is called.
Thank you.
import simpy
import random
class Patients:
def __init__(self, p_id):
self.id = p_id
self.bed_name = ""
self.admission_decision = ""
def admin_decision(self):
admin_decision_prob = random.uniform(0, 1)
if admin_decision_prob <= 0.7:
self.admission_decision = "DIS"
else:
self.dmission_decision = "IU"
return self.admission_decision
class Model:
def __init__(self, run_number):
self.env = simpy.Environment()
self.pt_ed_q = simpy.Store(self.env )
self.pt_counter = 0
self.tg = simpy.Resource(self.env, capacity = 4)
self.physician = simpy.Resource(self.env, capacity = 4)
self.bed_clean = simpy.Store(self.env)
self.bed_dirty = simpy.Store(self.env)
self.IU_bed = simpy.Resource(self.env, capacity = 50)
def generate_beds(self):
for i in range(77):
yield self.env.timeout(0)
yield self.bed_clean.put(f'bed{i}')
def generate_pt_arrivals(self):
while True:
self.pt_counter += 1
pt = Patients(self.pt_counter)
yield self.env.timeout(5)
self.env.process(self.process(pt))
def clean_beds_process(self, cleaner_id, pt):
while True:
print(pt.id)
bed = yield self.bed_dirty.get()
yield self.env.timeout(50)
yield self.bed_clean.put(bed)
def process(self, pt):
with self.tg.request() as req:
yield req
yield self.env.timeout(10)
bed = yield self.bed_clean.get()
pt.bed_name = bed
pt.admin_decision()
if pt.admission_decision == "DIS":
with self.IU_bed.request() as req:
dirty_bed_name = pt.bed_name
yield self.bed_dirty.put(dirty_bed_name)
yield self.env.timeout(600)
else:
dirty_bed_name = pt.bed_name
yield self.bed_dirty.put(dirty_bed_name)
def run(self):
self.env.process(self.generate_pt_arrivals())
self.env.process(self.generate_beds())
for i in range(2):
self.env.process(self.clean_beds_process(i+1, Patients))
self.env.run(until = 650)
run_model = Model(0)
run_model.run()
So if a patient can use either a clean bed or a dirty bed then the patient needs to make two request (one for each type of bed) and use env.any_of to wait for the first request to fire. You also need to deal with the case that both events fire at the same time. Don't forget to cancel the request you do not use. If the request that fires is for a clean bed, things stay mostly the same. But if the request is for a dirty bed, then you need to add a step to clean the bed. For this I would make the cleaners Resources instead of processes. So the patient would request a cleaner, and do a timeout for the cleaning time, release the cleaner. To collect patient data I would create a log with the patient id, key event, time, and crunch these post sim to get the stats I need. To process the log I often create a dataframe that filters the log for the first, a second dataframe that filters for the second envent, join the two dataframes on patient id. Now both events for a patient is on one row so I can get the delta. once I have have the delta I can do a sum and count. For example, if my two events are when a patient arrives, and when a patient gets a bed, get the sum of deltas and divide by the count and I have the average time to bed.
If you remember, one of the first answers I gave you awhile ago had a example to get the first available bed from two different queues
I do not have a lot of time right know, but I hope this dissertation helps a bit
At the moment I am creating doctors as following:
doctor = simpy.Resource(self.env, capacity=3)
These doctors are requested for a flow of patients using the following:
with self.doctor.request() as req:
yield req2
*patient encounter*
What I would like to do, is initialize the doctors with some kind of fatigue level (and other characteristics) like:
# Class representing a doctor and their characteristics
class Doctor(object):
def __init__(self, env, id, exp_level, fatigue):
self.env = env
self.id = id
self.exp_level = exp_level
self.fatigue = fatigue
self.isAvailable = True
Then in the encounter I would like to access the doctor to add fatigue such as:
with self.doctor.request() as req:
yield req2
*patient encounter*
self.doctor.fatigue += 5
Where once the fatigue crosses a threshold the doctor goes on break and the fatigue is reset, such as:
def monitor_break(self):
while True:
if Doctor.fatigue > 9:
Doctor.fatigue = 0
yield self.env.timeout(15)
Any idea on how to do this?
First I would use a store for your docs instead of using a resource. Second I would decorate the store with a get override to check if the doc needs a nap
something like this
"""
A simple sim where Doctors put themselves on break when they get tired
Programmer Michael R. Gibbs
"""
import simpy
import random
class Doctor():
"""
Doctor that get tired
"""
def __init__(self, env, id):
self.env = env
self.id = id
self.fatigue = 0
class Doc_Pool():
"""
decorates a simpy store
to chekc a doc's fatigue level
and doing a timeout if fatigue is too
high before returning the doc to the pool
"""
def __init__(self, env, num_of_docs, max_fatigue):
self.env = env
self.store = simpy.Store(env)
self.store.items = [Doctor(env, i+1) for i in range(num_of_docs)]
self.max_fatigue = max_fatigue
def get(self):
"""
get a doc from the real store
"""
return self.store.get()
def put(self, doc):
"""
put a doc in the store
unless the doc needs a break
"""
if doc.fatigue > self.max_fatigue:
# needs a break
self.env.process(self._doc_timeout(doc))
else:
self.store.put(doc)
def _doc_timeout(self, doc):
"""
gives the doc a break, then put in store
"""
print(f'{self.env.now:.0f} doctor {doc.id} starting break with fatigue {doc.fatigue:0.2f}')
yield self.env.timeout(doc.fatigue)
doc.fatigue = 0
self.store.put(doc)
print(f'{self.env.now:.0f} doctor {doc.id} ending break')
class Patient():
"""
Simple class to track patients
"""
next_id = 1
def __init__(self):
self.id = self.get_next_id()
#classmethod
def get_next_id (cls):
id =cls.next_id
cls.next_id += 1
return id
def gen_pats(env, doc_store):
"""
Generates the arrival off patients
and queue them up for a doctor visit
"""
while True:
pat = Patient()
print(f'{env.now:.0f} patient {pat.id} has arrived')
yield env.timeout(random.uniform(1,5))
env.process(doc_visit(env, pat, doc_store))
def doc_visit(env, pat, doc_store):
"""
gets a doc, do visit, add fatigue to doc, end visit
"""
doc = yield doc_store.get()
print(f'{env.now:.0f} patient {pat.id} is visiting doctor {doc.id}')
env.timeout(random.uniform(1,6))
doc.fatigue += random.uniform(1,5)
doc_store.put(doc)
print(f'{env.now:.0f} patient {pat.id} visiting doctor {doc.id} has ended')
# boot up sim
env = simpy.Environment()
doc_store = Doc_Pool(env, 3, 10)
env.process(gen_pats(env, doc_store))
env.run(200)
print('end of simulation')
I was trying to make a recursive realtime simulation to see it it was possible within the simpy framework.
the function was made to track into a log dictionary...
The jupyter kernel stopped working and shutdown when I ran this code, why?
def simStudy(env,AVERAGE_PROGRAMME_LENGTH):
i = 0
while i < int(3000/TYPING_RATE):
ans = input("Target achieved?")
log[env.now] = int(bool(ans))
print(log)
AVERAGE_PROGRAMME_LENGTH -= TYPING_RATE
yield env.timeout(1)
env = sim.rt.RealtimeEnvironment(factor = 10,strict = True)
def startSim():
try:
env.process(simStudy(env,AVERAGE_PROGRAMME_LENGTH))
env.run()
except RuntimeError as e:
startSim()
print(str(e)[-8:-3])
startSim()
I do not use recursion very often in simulation. Most of my processes will have a queue and if a entity needs the same process, I just put it back into the queue and let the queue processor process it multiple times.
The example is pealing layers of a onion one layer at a time
here it is with recursion
"""
demonstrates how recurssion can be used in simpy
by simulating the pealing of a onion
programmer: Michael R. Gibbs
"""
import simpy
import random
class Onion():
"""
Simple onion object
id: unique oning id
layers: number of layers to peal
"""
# class var for generating ids
id = 0
def __init__(self):
"""
initialize onion with unique id and random number of layers
"""
Onion.id += 1
self.id = Onion.id
self.layers = random.randint(5,9)
def get_onions(env, res):
"""
sim startup
generate a bunch of onions to be pealed and
start pealing them
"""
for i in range(5):
onion = Onion()
env.process(peal_onion_layer(env,res,onion,0))
# note the lack of of a yeild so all onions get processed in parallel
def peal_onion_layer(env,res,onion,pealed):
"""
gets a pealer resource and peals one onion layer
and release pealer.
will need to get the pealer resource again if another layer
needs to be pealed
"""
with res.request() as req: # Generate a request event
yield req # Wait for access
yield env.timeout(1) # peal
# release resource
pealed += 1
print(f"{env.now} onion: {onion.id} pealed layer {pealed}, {onion.layers - pealed} layers to go")
if onion.layers <= pealed:
#finish, stop recursion
print(f"{env.now} onion: {onion.id} is pealed, {pealed} layers")
else:
# still have layers to peal, recurse
yield env.process(peal_onion_layer(env,res,onion,pealed))
# do any post recurse acions here
print(f"{env.now} onion: {onion.id} exiting layer {pealed}")
# start up recursion
env = simpy.Environment()
res = simpy.Resource(env, capacity=2)
get_onions(env,res)
env.run()
Here is the same sim without recursion
"""
demonstrates how queue can be used instead of recursion
by simulating the pealing of a onion
programmer: Michael R. Gibbs
"""
import simpy
import random
class Onion():
"""
Simple onion object
id: unique oning id
layers: number of layers to peal
"""
# class var for generating ids
id = 0
def __init__(self):
"""
initialize onion with unique id and random number of layers
"""
Onion.id += 1
self.id = Onion.id
self.layers = random.randint(5,9)
def get_onions(env, res):
"""
sim startup
generate a bunch of onions to be pealed and
start pealing them
"""
for i in range(5):
onion = Onion()
env.process(peal_onion_layer(env,res,onion,0))
# note the lack of of a yeild so all onions get processed in parallel
def peal_onion_layer(env,res,onion,pealed):
"""
gets a pealer resource and peals one onion layer
and release pealer.
will need to get the pealer resource again if another layer
needs to be pealed
"""
with res.request() as req: # Generate a request event
yield req # Wait for access
yield env.timeout(1) # peal
# release resource
pealed += 1
print(f"{env.now} onion: {onion.id} pealed layer {pealed}, {onion.layers - pealed} layers to go")
if onion.layers <= pealed:
#finish, stop recursion
print(f"{env.now} onion: {onion.id} is pealed, {pealed} layers")
else:
# still have layers to peal, recurse
env.process(peal_onion_layer(env,res,onion,pealed))
# start up recursion
env = simpy.Environment()
res = simpy.Resource(env, capacity=2)
get_onions(env,res)
env.run()
I am working on a python simulation using Simpy but I am having somewhat of a brick wall. The simulation has an arrival process and a shipping process. The goal is to ship as many orders as they arrive and not leave orders waiting. This code gives me a process that ships more orders than they arrive.
class LC:
def __init__(self,env):
self.dispatch=simpy.Container(env,capacity=100000, init=0)
self.costs=simpy.Container(env, capacity=100000, init=0)
self.shipment_count=simpy.Container(env,capacity=10000, init=0)
self.orders=simpy.Container(env,capacity=10000,init=0)
def shipping(env, ship):
while True:
#yield env.timeout(i)
print('Shipment was sent at %d' % (env.now))
time,weight=get_weight_n_time() ####other function that assigns weight and time of shipment
if weight <=35:
cost=get_cost(weight,0)###other function that calculates costs
else:
cost=get_cost(weight,1)
yield env.timeout(time)
print ('Shipment of %d kgs where delivered after %d days with cost of $ %d' %(weight,time,cost))
yield ship.dispatch.put(weight)
yield ship.orders.get(1)
yield ship.costs.put(cost)
yield ship.shipment_count.put(1)
def arrival (env,ship):
while True:
print('Order arrived at %d'%(env.now))
yield ship.orders.put(1)
yield env.timeout(1)
def shipment_gen(env,ship):
env.process(arrival(env,ship))
num_trucks=5
for i in range(num_trucks):
env.process(shipping(env,ship))
yield env.timeout(0)
env = simpy.Environment()
ship=LC(env)
env.process(shipment_gen(env,ship))
env.run(until=100)
when I add a variable to determine weather or not to make a shipment based in the # of orders then the shipment process does not occur
class LC:
def __init__(self,env):
self.dispatch=simpy.Container(env,capacity=100000, init=0)
self.costs=simpy.Container(env, capacity=100000, init=0)
self.shipment_count=simpy.Container(env,capacity=10000, init=0)
self.orders=simpy.Container(env,capacity=10000,init=0)
self.order=0
def shipping(env, ship):
while True:
#yield env.timeout(i)
print('Shipment was sent at %d' % (env.now))
time,weight=get_weight_n_time()
if weight <=35:
cost=get_cost(weight,0)
else:
cost=get_cost(weight,1)
yield env.timeout(time)
print ('Shipment of %d kgs where delivered after %d days with cost of $ %d' %(weight,time,cost))
yield ship.dispatch.put(weight)
yield ship.orders.get(1)
yield ship.costs.put(cost)
yield ship.shipment_count.put(1)
def arrival (env,ship):
while True:
print('Order arrived at %d'%(env.now))
yield ship.orders.put(1)
yield env.timeout(1)
ship.order+=1
def shipment_gen(env,ship):
env.process(arrival(env,ship))
# if ship.orders.level >0:
# num_trucks=get_number_trucks(ship)
# else:
# num_trucks=get_number_trucks(ship)
num_trucks=5
for i in range(num_trucks):
if ship.order>0:
env.process(shipping(env,ship))
yield env.timeout(0)
ship.order-=1
else:
print('-')
env = simpy.Environment()
ship=LC(env)
env.process(shipment_gen(env,ship))
env.run(until=100)
You do not need to add extra ship.order in the class. The container has the information about the number of orders(ship.orders.level).So delete the ship.order and delete the loop in shipment_gen ,your code will be executed normally. By using of multi-thread,the multi-process is realized by Simpy. If you add extra variable in the ship class,it may lead to errors.
do you want to move your
yield ship.orders.get(1)
line to the top of the function so your truck waits for a order to arrive before it does anything else?
also
shipment_gen is doing its if statement at time 0, but your order counter is not =+1 till time 1
so at time 0 your order counter is still 0 so your if will always do the else
I'm using Simpy for discrete event simulation and am having problems with what I can best describe as optional resources. The context is that I am simulating missions which will be undertaken and will demand some assets (resources) to undertake that mission. Unlike most SimPy implementations, the mission needs to start at the allocated time or else fails and may accept lesser resources to enable the mission to start.
For example a mission requires four vehicles at time = t. At time t only three vehicles are available, so the mission commences with three vehicles but has a lesser outcome. If only two or less vehicles are available, the mission would not go ahead and would be considered as failed.
Sorry for the lack of code in this example, I'm struggling to get my head around it. Any help would be appreciated.
I used containers to track resources, but take a look and let me know if this is what you want
"""
Demos checking the availability of resources befor taking resouces
Each agent has a first choice and second choice for seizing resources
if neither choice is avalale will not wait and skip getting resources
programmer: Michael R. Gibbs
"""
import simpy
import random
def eval(choice, containers):
"""
steps through each requirement and checks if there is enough resouces is available
returns True if there is enough, else returns False
"""
for k,v in choice.items():
if containers[k].level < v:
# not enough
return False
return True
def mission(env, id, firstChoice, secondChoice, startDelay, missingTime, containers):
"""
Sims agent checking if first or second choice of resouces is available
If so, will seize the resources, hold for a time, and then release the resouces
"""
choice = None # which choice to use
# wait for mission start
yield env.timeout(startDelay)
print()
print(f'{env.now} agent {id} is starting a mission')
x = [(k, v.level) for k, v in containers.items()]
print(f'available: {x}')
print(f'first choice {firstChoice}')
print(f'second choice {secondChoice}')
# evaluate the choices and see if either will work
if eval(firstChoice, containers):
choice = firstChoice
print(f'{env.now} agent {id} is going with first Choice')
else:
if eval(secondChoice,containers):
choice = secondChoice
print(f'{env.now} agent {id} is going with second Choice')
else:
print(f'{env.now} agent {id} is abort because not enough resources')
if choice is not None:
# found a choice that works
# seize the resouces, we checked that they are availale, so there should not be any queue time
for k,v in choice.items():
containers[k].get(v)
yield env.timeout(missingTime)
# return resouces
for k,v in choice.items():
containers[k].put(v)
print(f'{env.now} agent {id} has finished the mission ')
def build_choice():
"""
quick helper to generate random first and second choice requirements
(may not use all three resouces)
returns a dictionary of the requirements where key=type, value=how much
"""
choice = {}
while len(choice) == 0:
for k in ['A','B','C']:
v = random.randint(0,3)
if v > 0:
choice[k] = v
return choice
def gen_missions(env, containers):
"""
Generate a series of agents to seize the resources
"""
i = 0
while True:
i += 1 # id generator
firstChoice = build_choice()
secondChoice = build_choice()
env.process(mission(env,i,firstChoice,secondChoice,random.randint(1,4),random.randint(2,8),containers))
# put some time between agents
yield env.timeout(1)
env = simpy.Environment()
# load the resouces
containers = {}
containers['A'] = simpy.Container(env, init=10, capacity=10)
containers['B'] = simpy.Container(env, init=10, capacity=10)
containers['C'] = simpy.Container(env, init=10, capacity=10)
# start generating agents
env.process(gen_missions(env,containers))
env.run(100)