-1

I'm currently trying to simulate a store in Discrete Event Simulation using Simply. Due to the fact that my python knowledge is limited I am stuck with a problem. The store repairs broken products for their customers, however, my simulation is not working as intended. I’ve tried to find a similar problem on stack overflow, but I could not find it, therefore I am asking. If a similar question has already been solved could you please provide the link?

I am simulating a shop where customers arrive with their broken product, a cashier helps them by swapping the broken product for a repaired product. The customer leaves with a repaired product and the broken product is later repaired in the store. After it is repaired it can be used to help another customer.

The problem I experience is in the bottleneck of my simulation, which is the repair of the product. When a repairman is requested, python immediately assumes the requested repairman is busy. However, in my simulation there are some conditions the must be fulfilled before the repairman can start with the repair. One of these conditions is that a repairman must be available to repair the product, however since python assumes the repairman is busy after the request the process cannot commence since the condition cannot be met. If I would not request a repairman before my while statement the queue would not be recorded correctly, therefore I opted to do it this way.

I have provided a slimmed down piece of my entire code in which the problem occurs. In the code provided the problem occurs when the third broken product should be repaired. This is because the simulation has 3 repairmen available. If I would have 8 repairmen available the problem would occur for the eighth broken product.

Are there ways to solve or circumvent my problem? Thank you in advance.

import simply
class Store(object):
def __init__(self, env, num_cashiers, num_repairman):
    self.env = env
    
    self.num_cashiers = num_cashiers
    self.num_repairman = num_repairman
    self.cashiers = simpy.Resource(env, capacity=num_cashiers)
    self.repairman = simpy.Resource(env, capacity=num_repairman)
    
    self.total_customers = 0
    self.broken_product = 0
    self.repaired_product = 25
    
    self.resource_data = []
    
    


def customer_generator(self, env):

    customer_buffer = 5
    product_id = 1
    customer_interarrival_time = 100
    
    while True: #always generate customers during simulation

        if len(self.cashiers.queue) >= customer_buffer:     #if customer queue is full, customer is lost
            self.lost_customers +=1
    
        else: #If queue not full, start the process in the store
           
            env.process(self.store_operations(env, product_id))

        yield env.timeout(customer_interarrival_time) #wait for next customer
        product_id+=1
        self.total_customers +=1

def serve_customer(self, product_id):
    
    yield self.env.timeout(90)
    self.broken_product +=1
    self.repaired_product -=1
       
def repair_product(self, product_id):
    
    yield self.env.timeout(300)
    self.broken_product -=1
    self.repaired_product +=1

def store_operations(self, env, product_id):
    '''This function is where the problem persists
    While statement 1 exists the check if there are repaired products and a cashier available to help the customer
    otherwise timeout until both are available
    
    if statement 1 helps the customer by swapping a broken product for a repaired product
    
    while statement 2 exists to check if there are broken product to be repaired and whether a repairman is available
    otherwise timeout until both are available
    
    if statement 2 is where the repairman repairs a broken product'''
    
    # print(env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy cashiers', self.cashiers.count, ', cashier q', len(self.cashiers.queue))
    request_cashier = self.cashiers.request()
    yield request_cashier
    # print(env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy cashiers', self.cashiers.count, ', cashier q', len(self.cashiers.queue))
    while self.repaired_product==0 or self.cashiers.count==self.num_cashiers: 
        yield env.timeout(1)
    
    if self.repaired_product>0 and self.cashiers.count<self.num_cashiers:
        # print(env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy cashiers', self.cashiers.count, ', cashier q', len(self.cashiers.queue))
        yield env.process(self.serve_customer(product_id))
        self.cashiers.release(request_cashier)
        # print(env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy cashiers', self.cashiers.count, ', cashier q', len(self.cashiers.queue))

    print('product', product_id, 't before req', env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy repairmen', self.repairman.count, ', cashier q', len(self.repairman.queue))
    request_repair = self.repairman.request()
    yield request_repair                     
    print('product', product_id,'t after req', env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy repairmen', self.repairman.count, ', cashier q', len(self.repairman.queue))
    
    while self.broken_product==0 or self.repairman.count==self.num_repairman:
        yield env.timeout(1)
        print(env.now,'product', product_id,  'wait for repairman')         
    
    if self.broken_product>0 and self.repairman.count<self.num_repairman:

        print('product', product_id,'t before repair proces', env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy repairmen', self.repairman.count, ', cashier q', len(self.repairman.queue))
        yield env.process(self.repair_product(product_id))
        self.repairman.release(request_repair)
        print('product', product_id,'t after repair process',env.now, ', RP inv', self.repaired_product, ', BP inv', self.broken_product, ', busy repairmen', self.repairman.count, ', cashier q', len(self.repairman.queue))
    
    self.resource_data.append({
        'time' : env.now,
        'broken product inv' : self.broken_product,
        'repaired product inv': self.repaired_product,
        'busy cashiers' : self.cashiers.count,
        'cashier queue' : len(self.cashiers.queue),
        'busy repairmen' : self.repairman.count,
        'repairmen queue' : len(self.repairman.queue)})
    
    return self.resource_data


    env = simpy.Environment()

    store1 = Store(env, 3, 3)
    env.process(store1.customer_generator(env))


    env.run(500)


    print(store1.resource_data[0])
    print(store1.resource_data[1])
    print(store1.resource_data[2])
    print(store1.resource_data[3])
    print(store1.resource_data[4])
    print(store1.resource_data[5])
    print(store1.resource_data[6])


      
Boblo
  • 1
  • 1

1 Answers1

0

I broke this down into two independent processes. One process is repair workers fixing broken lamps that show up in their repair queue. The other is the cashier that takes broken lamps from customers and puts them in the repair queue, and hand out repaired lamps from the fixed queue. So this shows how two independent process can use Stores to pass work back and forth

"""
Quick simulation of a lamp exchange shop

Customer arrives with a broke lamp
If a repaired lamp is avalable, 
customer exchnage boken for repaired lamp and leaves
If a repaired lamp is not avalable, 
customer drops off boken lamp for repair and gets a raincheck returns later

Customer arrives with a raincheck after dropping of a broken lamp
if a repaired lamp is availale, customer takes repaired lamp and leaves
if a repaired lamp is not available customer returns later

When a broken lamp is droped off 
a resource pool of repair workers repairs the lamp and makes
the repaired lamp available for exchange

Programmer: Michael R. Gibbs
"""

import simpy
import random

class Lamp():
    """
    Quick class to collect stats on lamp rapair
    """

    lamp_cnt = 0 # used to gnerate unique lamp ids

    def __init__(self, state='broken'):
        """
        Init class vars, gen new id
        """

        Lamp.lamp_cnt += 1
        self.lamp_id = Lamp.lamp_cnt
        self.state = state # possible states: broken, repaired

class Lamp_Store():
    """
    Models the serving of customers with cashiers
    and the repairing of broken lamps with repair workers
    """
    
    def __init__(self, env, num_cashieres=2, num_repair_workers=3, starting_lamp_stock=2, max_queue_len=5):
        """
        Sets up the resurce pools and stock of repaired lamps
        """

        self.env = env

        self.max_queue_len = max_queue_len

        # cashiers that service customers with lamps
        self.cashiers = simpy.Resource(env, capacity=num_cashieres)

        # start repair workers repairing lamps (save for future enchancements)
        self.reqair_workers = [env.process(self.repair_lamps(id + 1)) for id in range(num_repair_workers)]

        # stores for broken and repair lamps
        self.broken_lamps = simpy.Store(env)
        self.repaired_lamps = simpy.Store(env)


        # sead the store of repaired lamps with some lamps
        self.repaired_lamps.items = [Lamp(state='repaired') for _ in range(starting_lamp_stock)]

    def customer_arrives(self, customer):
        """
        Start the processing of the customer by
        putting them in a queue for a cashier
        """

        if len(self.cashiers.queue) >= self.max_queue_len:
            # line too long, lost customer
            print(f'{self.env.now} customer {customer.customer_id} has arrived queue has {len(self.cashiers.queue)} customers, too long, leaving')

        else:
            while customer.lamp is None or customer.lamp.state == 'broken':
                # still need a repaired lamp

                print(f'{self.env.now} customer {customer.customer_id} has arrived queue has {len(self.cashiers.queue)} customers')

                # chekc queue size
                if len(self.cashiers.queue) >= self.max_queue_len:
                    # queue too long
                    # come back later
                    print(f'{self.env.now} customer {customer.customer_id} line too long, come back later')
                else:
                    # get service
                
                    # still needs a repaired lamp
                    yield self.env.process(self.cashier_serves_customer(customer))
                
                if customer.lamp is None or customer.lamp.state == 'broken':
                    # need to come back for lamp
                    yield self.env.timeout(random.randint(5,15))
                    print(f'{self.env.now} customer {customer.customer_id} return to shop')

    def cashier_serves_customer(self, customer):
        """
        wait in queue for cashier
        exchange lamp, or get raincheck
        """

        # wait for next available cashier
        with self.cashiers.request() as req:
            yield req

            # got cashier
            if customer.lamp is not None:
                #drop off broken lamp
                yield env.timeout(random.randint(4,10))
                lamp = customer.lamp
                self.broken_lamps.put(customer.lamp)
                customer.lamp = None
                print(f'{self.env.now} customer {customer.customer_id} dropped off borken lamp {lamp.lamp_id}')

            if len(self.repaired_lamps.items) > 0:
                # have a repaired lamp to exchnge
                customer.lamp = yield self.repaired_lamps.get()
                print(f'{self.env.now} customer {customer.customer_id} got repaired lamp {customer.lamp.lamp_id}')

            else:
                # no lamps, rain check
                customer.has_raincheck = True

    def repair_lamps(self, worker_id):
        """
        Models a repair worker wainting for a broken lamp
        and fixing it
        """

        while True:
            # wait for next borken lamp
            lamp = yield self.broken_lamps.get()
            print(f'{self.env.now} worker {worker_id} has started to repair lamp {lamp.lamp_id}')

            # fix broken lamp
            yield self.env.timeout(int(random.triangular(5,14,15)) + 1)

            # make fixed lamp available for exchange
            lamp.state = 'repaired'

            self.repaired_lamps.put(lamp)
            print(f'{self.env.now} worker {worker_id} has finished repairing lamp {lamp.lamp_id}')

            
class Customer():
    """
    Customer trying to exchange a broken lamp
    """
    customer_cnt = 0

    def __init__(self):

        Customer.customer_cnt += 1
        self.customer_id = Customer.customer_cnt

        self.lamp = Lamp(state='broken')

def gen_customers(env,store):
    """
    Generate customers with broken lamps and send to store
    """

    while True:
        yield env.timeout(random.randint(1,3))
        cust = Customer()

        env.process(store.customer_arrives(cust))


# boot up the simulation
env = simpy.Environment()
store = Lamp_Store(env)    

env.process(gen_customers(env, store))

env.run(200)
Michael
  • 1,671
  • 2
  • 4
  • 8