4

I want to create a simulation model that simulates a bank with 3 number of counters. I want there to be one queue for customers, and if any one of the counters is available, it will service the counter for some amount of time. Each counter is its own simpy resource (I cant do one resource with a capacity of 3), and I need to know which counter serves the customer. I am having trouble implementing this.

I have found this article which seems to be about the same problem, but still don't know the exact implementation http://comments.gmane.org/gmane.comp.python.simpy.user/1754

I assume I need a yield simpy.AnyOf(env, list_of_resources) in there to request the first available counter, but I cant figure out how to set this up properly, and also if more than one resource becomes available at the same time, I want some way of checking this so that I can choose which counter is better for the customer to go to.

I am running python version 2.7, simpy version 3.0.4

EDIT Adding Code that show what Im trying to do and how it doesnt work right now.

import simpy
import random

LENGTH_SIM = 200.0

class Customer(object):
    def __init__(self, arrive_time, num):
        self.arrive_time = arrive_time
        self.start_service_time = -1.0
        self.finish_service_time = -1.0
        self.served_by = -1.0
        self.num = num
    def print_attributes(self):
        format_string = "#%3d  Arrive: %6.1f  Start Service: %6.1f  "\
        + " End Service: %6.1f" + "   Served By: %3d"
        print format_string %(self.num, self.arrive_time,
                              self.start_service_time,
                                self.finish_service_time, self.served_by)

class Counter(object):
    def __init__(self, number):
        self.number = number
        self.name = "Counter " + str(number)
        self.start_times = []
        self.end_times = []

class Bank(object):
    def __init__(self, env, num_counters):
        self.counters = [Counter(x+1) for x in range(num_counters)]
        self.num_counters = num_counters
        self.counters_resources = [simpy.Resource(env, capacity=1)
                                   for x in range(num_counters)]


def generate_customers(env, customers):
    count = 1
    while 1:
        wait_time = random.randint(8, 12)
        yield env.timeout(wait_time)
        customers.append(Customer(env.now, count))
        print "Generated Customer: ", count
        count += 1

def select_counter(env, bank):
    '''choose a counter for the customer'''
    for x in range(len(bank.counters)):
        if bank.counters_resources[len(bank.counters) - 1 - x].count == 0:
            print "Counter Selected: ", len(bank.counters) - 1 - x
            print bank.counters[len(bank.counters) - 1 - x].number
            print bank.counters_resources[len(bank.counters) - 1 - x].users
            return len(bank.counters) - 1 - x
    return -2.0

def gen_service_time():
    return random.random() * 20.0 + 21.0

def handle_customer(env, bank, customer, customers, num_process):
    '''handles customer'''
    print "Process ", num_process, " started at:", env.now
    counter = select_counter(env, bank)
    print "Process ", num_process, " after counter select:", env.now
    if counter != -2.0:
        with bank.counters_resources[counter].request() as req:
            yield req
            print "Process ", num_process, "Time", env.now, "In if"
            bank.counters[counter].start_times.append(env.now)
            service_time = gen_service_time()
            customer.start_service_time = env.now
            customer.finish_service_time = env.now + service_time
            bank.counters[counter].end_times.append(env.now + service_time)
            customer.served_by = counter + 1
            yield env.timeout(service_time)
    else:
        reqs = []
        for x in range(len(bank.counters)):
            reqs.append(bank.counters_resources[x].request())
        counter_used = yield simpy.events.AnyOf(env, reqs)
        for x in range(len(reqs)):
            if counter_used.keys()[0] != reqs[x]:
                print "True"
                bank.counters_resources[x].release(reqs[x])
        print "Process ", num_process, "Time", env.now, "In else"
        bank.counters[0].start_times.append(env.now)
        service_time = gen_service_time()
        customer.start_service_time = env.now
        customer.finish_service_time = env.now + service_time
        bank.counters[0].end_times.append(env.now + service_time)
        customer.served_by = 1
        yield env.timeout(service_time)
        for x in range(len(reqs)):
##            if counter_used.keys()[0] == reqs[x]:
            if 1:
                bank.counters_resources[x].release(reqs[x])




def run_bank(env, bank, customers):
    while 1:
        min_time = -1.0
        min_customer = -1.0
        for x in xrange(len(customers)):
            if customers[x].arrive_time < min_time or min_time == -1.0:
                if customers[x].arrive_time > env.now:
                    min_time = customers[x].arrive_time
                    min_customer = x
                else:
                    continue
        if min_time == -1.0:
            yield env.timeout(LENGTH_SIM - env.now)
        yield env.timeout(min_time - env.now)
        print "Calling Process: ", min_customer, "At time:", env.now
        env.process(handle_customer(env,bank,customers[min_customer],customers,
                                    min_customer + 1))

def run_sim():
    env = simpy.Environment()
    customers = []
    env.process(generate_customers(env, customers))
    env.run(until=LENGTH_SIM)
    env2 = simpy.Environment()
    bank = Bank(env2, 3)
    env2.process(run_bank(env2, bank, customers))
    env2.run(until = LENGTH_SIM)
    for x in range(len(customers)):
        customers[x].print_attributes()

if __name__ == '__main__':
    run_sim()
CameronB
  • 83
  • 8
  • Why can’t you use a Resource with a capactiy of 3? – Stefan Scherfke Jul 31 '14 at 06:57
  • Because each of the counters has a difference, and it is only if all of them are being used and a queue forms do I want them to act like a resource with capacity free (ie a customer goes to the first one available), otherwise I want to control which of the available counters the customer goes to. – CameronB Jul 31 '14 at 14:32

2 Answers2

2

If you really can’t use a normal Resource with a capacity of 3, maybe Store would help you. The Store stores counter objects which you can request. Each counter has its own identity and can, for example, track its number of usages. After a customer is done, it has to put the counter back into the Store:

import simpy


class Counter:
    usages = 0


def customer(env, counters):
    counter = yield counters.get()
    yield env.timeout(1)
    counter.usages += 1
    yield counters.put(counter)


env = simpy.Environment()
counters = simpy.Store(env, capacity=3)
counters.items = [Counter() for i in range(counters.capacity)]
for i in range(10):
    env.process(customer(env, counters))
env.run()
for counter in counters.items:
    print(counter.usages)
Stefan Scherfke
  • 3,012
  • 1
  • 19
  • 28
  • That would work, but is there any way for the counter = yield counters.get() line in the customer function, to let me choose which counter I go to if there are more than one available? – CameronB Jul 31 '14 at 14:31
  • If you check the answer I posted, I seem to have gotten it to work. As an aside, are you one of the developers of simpy? I've seen you post a lot in the development threads about it. I ask because I found a possible bug (intended feature?), in my code the line if bank.counters_resources[len(bank.counters) - 1 - x].count == 0 and\ bank.counters_resources[len(bank.counters) - 1 - x].queue == []: In the select counter function, if I don't have the second part after the and, then if something checks for a resource at the same environment time as the resource is released – CameronB Jul 31 '14 at 20:37
  • Then the count will equal 0 (since the resource is no longer being used) even though there is a queue of of things requesting the resource! What I mean is the next thing in the queue requesting use of the resource is not immediately given the resource after the previous thing releases control of the resource. I'm not sure if this is intentional or if there is even a way to fix it, but to me it seems like it may not be intended behavior. – CameronB Jul 31 '14 at 20:38
  • With FilterStore you can filter the available counters you can get by user-defines criteria. – Stefan Scherfke Aug 01 '14 at 11:28
  • Regarding the bug/feature report – please send a mail to our mailing list for further discussion. – Stefan Scherfke Aug 01 '14 at 11:29
1

I managed to solve it myself, the only thing I don't understand is what the different arguments to the cancel method do (I just set them all as None and it works, I'm not sure where there is documentation on what they are besides their name). The fixed code is as follows (the relevant part being the else clause in the handle_customer function:

import simpy
import random

LENGTH_SIM = 100.0

class Customer(object):
    def __init__(self, arrive_time, num):
        self.arrive_time = arrive_time
        self.start_service_time = -1.0
        self.finish_service_time = -1.0
        self.served_by = -1.0
        self.num = num
    def print_attributes(self):
        format_string = "#%3d  Arrive: %6.1f  Start Service: %6.1f  "\
        + " End Service: %6.1f" + "   Served By: %3d"
        print format_string %(self.num, self.arrive_time,
                              self.start_service_time,
                                self.finish_service_time, self.served_by)

class Counter(object):
    def __init__(self, number):
        self.number = number
        self.name = "Counter " + str(number)
        self.start_times = []
        self.end_times = []

class Bank(object):
    def __init__(self, env, num_counters):
        self.counters = [Counter(x+1) for x in range(num_counters)]
        self.num_counters = num_counters
        self.counters_resources = [simpy.Resource(env, capacity=1)
                                   for x in range(num_counters)]


def generate_customers(env, customers):
    count = 1
    while 1:
##        wait_time = random.randint(8, 12)
        wait_time = 2
        yield env.timeout(wait_time)
        customers.append(Customer(env.now, count))
        print "Generated Customer: ", count
        count += 1

def select_counter(env, bank):
    '''choose a counter for the customer'''
    for x in range(len(bank.counters)):
        if bank.counters_resources[len(bank.counters) - 1 - x].count == 0 and\
        bank.counters_resources[len(bank.counters) - 1 - x].queue == []:
            print "Counter Selected: ", len(bank.counters) - 1 - x
            return len(bank.counters) - 1 - x
    return -2.0

def gen_service_time():
##    return random.random() * 20.0 + 19.0
    return random.randint(4, 8)

def wait_til_available(env, bank, temp_list):
    while 1:
        yield env.timeout(0.0001)
        for x in range(len(bank.counters)):
            if bank.counters_resources[x].count == 0:
                req = bank.counters_resources[x].request()
                temp_list[0] = x
                temp_list[1] = req
                return 

def handle_customer(env, bank, customer, customers, num_process):
    '''handles customer'''
    print "Process ", num_process, " started at:", env.now
    counter = select_counter(env, bank)
    if counter != -2.0:
        with bank.counters_resources[counter].request() as req:
            yield req
            bank.counters[counter].start_times.append(env.now)
            service_time = gen_service_time()
            customer.start_service_time = env.now
            customer.finish_service_time = env.now + service_time
            bank.counters[counter].end_times.append(env.now + service_time)
            customer.served_by = counter + 1
            yield env.timeout(service_time)
    else:
        reqs = []
        got_counter = 0
        for x in range(len(bank.counters)):
            reqs.append(bank.counters_resources[x].request())
        good_req = yield simpy.events.AnyOf(env, reqs)
        req = good_req.keys()[0]
        for x in range(len(bank.counters)):
            if req != reqs[x]:
                bank.counters_resources[x].release(reqs[x])
                reqs[x].cancel(None, None, None)
            else:
                got_counter = x
        bank.counters[got_counter].start_times.append(env.now)
        service_time = gen_service_time()
        customer.start_service_time = env.now
        customer.finish_service_time = env.now + service_time
        bank.counters[got_counter].end_times.append(env.now + service_time)
        customer.served_by = got_counter + 1
        yield env.timeout(service_time)
        print "Process ", num_process, " finished at:", env.now
        bank.counters_resources[got_counter].release(req)




def run_bank(env, bank, customers):
    while 1:
        min_time = -1.0
        min_customer = -1.0
        for x in xrange(len(customers)):
            if customers[x].arrive_time < min_time or min_time == -1.0:
                if customers[x].arrive_time > env.now:
                    min_time = customers[x].arrive_time
                    min_customer = x
                else:
                    continue
        if min_time == -1.0:
            yield env.timeout(LENGTH_SIM - env.now)
        yield env.timeout(min_time - env.now)
        print "Calling Process: ", min_customer+1, "At time:", env.now
        env.process(handle_customer(env,bank,customers[min_customer],customers,
                                    min_customer + 1))

def run_sim():
    env = simpy.Environment()
    customers = []
    env.process(generate_customers(env, customers))
    env.run(until=LENGTH_SIM)
    env2 = simpy.Environment()
    bank = Bank(env2, 3)
    env2.process(run_bank(env2, bank, customers))
    env2.run(until = LENGTH_SIM)
    for x in range(len(customers)):
        customers[x].print_attributes()

if __name__ == '__main__':
    run_sim()
CameronB
  • 83
  • 8