[Python] Simpy model with closure, entities grabbing resource while closing code running

Discussão em 'Python' iniciado por Stack, Junho 19, 2025.

  1. Stack

    Stack Membro Participativo

    I have a model where:


    • resources are available between opening hours (8am-8pm).


    • Any entities using resources are kicked out at 1 minute to 8pm, then a closing function is run to claim all the resources with a -1 priority for 12 hours until 8am


    • The resources stop accepting new requests at 6pm:

      • entities who arrive between 6-8pm have a timeout until 8:01pm before requesting a resource, by which time the shop should be closed and they can't claim one until the shop is open again at 8am.


      • If an entity doesn't get a resource during the day before 6pm, they timeout for ~2 hours until close where the shop should be closed and they wait for a resource to become available again when the shop opens at 8am.

    My MRE is below (its quite long, and may need a few runs to reproduce the issue due to randomness in the model):

    import simpy
    import random
    import math
    import numpy as np

    class default_params():
    run_time = 10080
    iterations = 1
    no_resources = 4
    inter_arr = 120
    resource_time = 240
    #shop Opening Hours
    shop_open_time = 8
    shop_stop_accept = 18
    shop_close_time = 20

    class spawn_entity:
    def __init__(self, p_id):
    self.id = p_id
    self.arrival_time = np.nan
    self.resource_gained_time = np.nan
    self.leave_time = np.nan

    class shop_model:
    def __init__(self, run_number, input_params):
    #start environment, set entity counter to 0 and set run number
    self.env = simpy.Environment()
    self.input_params = input_params
    self.entity_counter = 0
    self.run_number = run_number
    #establish resources
    self.resources = simpy.PriorityResource(self.env,
    capacity=input_params.no_resources)

    ##############################MODEL TIME###############################
    def model_time(self, time):
    #Function to work out day and hour in the model
    day = math.floor(time / (24*60))
    day_of_week = day % 7
    #If day 0, hour is time / 60, otherwise it is the remainder time once
    #divided by number of days
    hour = math.floor((time % (day*(24*60)) if day != 0 else time) / 60)
    return day, day_of_week, hour

    ###########################ARRIVALS##################################
    def arrivals(self):
    yield self.env.timeout(1)
    while True:
    #up entity counter and spawn a new entity
    self.entity_counter += 1
    p = spawn_entity(self.entity_counter)
    #begin entity to shop process
    self.env.process(self.entity_journey(p))
    #randomly sample the time until the next arrival
    sampled_interarrival = round(random.expovariate(1.0
    / self.input_params.inter_arr))
    yield self.env.timeout(sampled_interarrival)

    ######################### CLOSE SHOP ################################
    def close_shop(self):
    while True:
    #if first day, close shop until open time, then wait until close
    if self.env.now == 0:
    time_closed = self.input_params.shop_open_time
    time_out = self.input_params.shop_close_time
    else:
    #close for 12 hour overnight and time out process until next day
    time_closed = 12
    time_out = 24

    #Take away all the resources for the close time to simulate the shop
    # being closed.
    print(f'--!!!!!CLOSING SHOP AT {self.env.now} FOR {time_closed * 60} MINS!!!!!--')
    i=0
    for _ in range(self.input_params.no_resources):
    i += 1
    print(f'--claiming resource {i}')
    self.env.process(self.fill_shop(time_closed * 60))
    print(f'--!!!!!SHOP CLOSED!!!!!--')

    #Timout for timeout period until next closure.
    yield self.env.timeout(time_out * 60)

    def fill_shop(self, time_closed):
    #If close time, take away all the resources
    with self.resources.request(priority=-1) as req:
    yield req
    print(f'--resource claimed for close at {self.env.now} for {time_closed} mins')
    yield self.env.timeout(time_closed)

    ######################## ENTITY JOURNEY #############################

    def entity_journey(self, entity):
    #Arrival
    entity.arrival_time = self.env.now
    day, day_of_week, hour = self.model_time(entity.arrival_time)
    print(f'entity {entity.id} starting process at {entity.arrival_time}')

    #If arrival hour between stop accept and close, add an extra wait
    #to ensure they don't sneak in between these times
    if ((hour >= self.input_params.shop_stop_accept)
    and (hour < self.input_params.shop_close_time)):
    #Time out until shop close, where resources will all be claimed
    #then entity can wait until next open.
    next_close = ((day * 60 * 24)
    + (self.input_params.shop_close_time * 60))
    time_out = (next_close - entity.arrival_time) + 1
    print(f'entity {entity.id} arrived in queue after stop accepting. Time out {time_out} mins until close')
    yield self.env.timeout(time_out)
    print(f'entity {entity.id} has waited until close at {self.env.now}')

    #request resource
    i=1
    with self.resources.request(priority=1) as req:
    #Find out current model time
    time = self.env.now
    print(f'entity {entity.id} requesting resource at time {time} - attempt {i}')
    day, day_of_week, hour = self.model_time(time)
    #Work out the minutes until the next shop stop accept time (next day
    #if hour after stop accept hour)
    next_stop_accept_day = (day + 1 if hour
    >= self.input_params.shop_stop_accept
    else day)
    next_stop_accept = ((next_stop_accept_day * 60 * 24)
    + (self.input_params.shop_stop_accept * 60))
    time_to_stop_accept = (next_stop_accept - time) + 1

    print(f'entity {entity.id} has {time_to_stop_accept} mins until shop stops accepting')

    #entity either gets resource or timesout if past stop accept time
    yield req | self.env.timeout(time_to_stop_accept)

    #If entity doesn't get a bed the first attempt, keep trying
    #until they do
    while not req.triggered:
    i += 1
    print(f'entity {entity.id} did not get resource as shop stopped accepting at {self.env.now}, entity waiting 2 hours then rejoining queue for attempt {i}')
    #Time out for the time between stop accepting and close, to
    #ensure no entities get a resource during this time.
    yield self.env.timeout(((self.input_params.shop_close_time
    - self.input_params.shop_stop_accept)
    * 60) + 1)

    print(f'entity {entity.id} requesting resource at time {self.env.now} - attempt {i}')
    #entity tries again to get a resource until the next stop accept
    #time.
    time = self.env.now
    day, day_of_week, hour = self.model_time(time)
    next_stop_accept_day = (day + 1 if hour
    >= self.input_params.shop_stop_accept
    else day)
    next_stop_accept = ((next_stop_accept_day * 60 * 24)
    + (self.input_params.shop_stop_accept
    * 60))
    time_to_stop_accept = (next_stop_accept - time) + 1
    print(f'entity {entity.id} has {time_to_stop_accept} until shop stops accepting')

    #entity either gets resource or timesout if past stop accept time
    yield req | self.env.timeout(time_to_stop_accept)

    #Entity got resource, record the time and randomly sample
    #process time
    print(f'entity {entity.id} got resource at {self.env.now} on attempt {i}')
    entity.resource_gained_time = self.env.now
    #Time entity spends is randomly sampled, with them being kicked out
    #1 minute before 8pm.
    day, day_of_week, hour = self.model_time(self.env.now)
    next_close_day = (day + 1 if hour
    >= self.input_params.shop_close_time
    else day)
    next_close = ((next_close_day * 60 * 24)
    + (self.input_params.shop_close_time * 60)) - 1
    time_to_next_close = next_close - self.env.now
    sampled_shop_time = round(random.expovariate(1.0
    / self.input_params.resource_time))
    yield self.env.timeout(min(sampled_shop_time, time_to_next_close))

    #record entity leave time
    entity.leave_time = self.env.now

    ########################RUN#######################
    def run(self):
    self.env.process(self.arrivals())
    self.env.process(self.close_shop())
    self.env.run(until = self.input_params.run_time)

    def run_the_model(input_params):
    #run the model for the number of iterations specified
    for run in range(input_params.iterations):
    print(f"Run {run+1} of {input_params.iterations}")
    model = shop_model(run, input_params)
    model.run()

    run_the_model(default_params)


    Sometimes the close code runs perfectly, and all the resources are claimed at 8pm, which looks like:

    --!!!!!CLOSING SHOP AT 8400 FOR 720 MINS!!!!!--
    --claiming resource 1
    --claiming resource 2
    --claiming resource 3
    --claiming resource 4
    --!!!!!SHOP CLOSED!!!!!--
    --resource claimed for close at 8400 for 720 mins
    --resource claimed for close at 8400 for 720 mins
    --resource claimed for close at 8400 for 720 mins
    --resource claimed for close at 8400 for 720 mins


    The issue I have is that sometimes when I'm closing the shop, not all resources get claimed at exactly 8pm and some entity activity will cut in before all resources have been claimed, sometimes even claiming a resource for a short time! The print out where this happens looks something like:

    --!!!!!CLOSING SHOP AT 1200 FOR 720 MINS!!!!!--
    --claiming resource 1
    --claiming resource 2
    --claiming resource 3
    --claiming resource 4
    --!!!!!SHOP CLOSED!!!!!--
    --resource claimed for close at 1200 for 720 mins
    entity 8 requesting resource at time 1202 - attempt 2
    entity 8 has 1319 until shop stops accepting
    entity 9 requesting resource at time 1202 - attempt 2
    entity 9 has 1319 until shop stops accepting
    entity 10 requesting resource at time 1202 - attempt 2
    entity 10 has 1319 until shop stops accepting
    entity 8 got resource at 1202 on attempt 2
    entity 9 got resource at 1202 on attempt 2
    entity 10 got resource at 1202 on attempt 2
    --resource claimed for close at 1226 for 720 mins
    entity 11 starting process at 1265
    entity 11 requesting resource at time 1265 - attempt 1
    entity 11 has 1256 mins until shop stops accepting
    entity 12 starting process at 1457
    entity 12 requesting resource at time 1457 - attempt 1
    entity 12 has 1064 mins until shop stops accepting
    entity 13 starting process at 1462
    entity 13 requesting resource at time 1462 - attempt 1
    entity 13 has 1059 mins until shop stops accepting
    --resource claimed for close at 1510 for 720 mins
    --resource claimed for close at 1514 for 720 mins


    Here the first of the 4 resources gets closed exactly at 1200mins as expected, but the other 3 don't get claimed until 1226, 1510 and 1514. Entities 8, 9 and 10 all manage to claim a resource briefly before they get closed, and leave the model. I don't understanding how they are managing to jump ahead of the close in the queue, as the close is priority -1 compared to their priority of 1. Nor do I understand why they sometimes don't get claimed to so many minutes after the close. Any help greatly appreciated.

    Continue reading...

Compartilhe esta Página