pythonpython-3.xsimpy

Simulation in Simpy not working - yielded process never returns


I am trying to write a simple simulation of a queuing system:

  1. An input process generates customers regularly at a specified rate.
  2. A queuing system reacts when a customer is generated by the input process.

The input process is:

import simpy

class InputProcess(object):
    def __init__(self, env: simpy.Environment, name: str, freq: float):
        self.env = env
        self.name = name
        self.period = 1/freq
        self.counter = 0
        self.action = env.process(self.run())
    
    def create_cust(self):
        cust_name = f"{self.name}-{self.counter}"
        self.counter += 1
        return cust_name

    def run(self):
        while True:
            new_cust = self.create_cust()
            print("customer %s created at %d" % (new_cust.name, self.env.now))
            yield self.env.timeout(self.period, new_cust)

The process created out of run will emit an event every period units of time.

The queue:

from __future__ import annotations

from simpy import Environment
from simpy.events import AnyOf, Event

from input_process import InputProcess

class Queue(object):
    def __init__(self, env: Environment, period: float):
        self.env = env
        self.period = period
        self.inputs = []
        env.process(self.listen())

    def add_input(self, node: InputProcess):
        print("ADDED")
        self.inputs.append(node.action)

    def listen(self):
        while True:
            if len(self.inputs) > 0:
                print("YES")
                yield AnyOf(self.env, self.inputs)
                print("TRIGGERED")

The simulation:

import simpy

from input_process import InputProcess
from queue import Queue

def run():
    env = simpy.Environment()

    gen = InputProcess(env, "T1", 1/2)
    queue = Queue(env, 3)

    queue.add_input(gen)

    env.run(until=25)

if __name__ == "__main__":
    run()

The output:

ADDED
traffic generator originated job 'T1-0' at 0
YES
traffic generator originated job 'T1-1' at 2
traffic generator originated job 'T1-2' at 4
traffic generator originated job 'T1-3' at 6
traffic generator originated job 'T1-4' at 8
traffic generator originated job 'T1-5' at 10
traffic generator originated job 'T1-6' at 12
traffic generator originated job 'T1-7' at 14
traffic generator originated job 'T1-8' at 16
traffic generator originated job 'T1-9' at 18
traffic generator originated job 'T1-10' at 20
traffic generator originated job 'T1-11' at 22
traffic generator originated job 'T1-12' at 24

The problem

As you can see, the queue tries to pause and wait for any input's events. In the current setting, there is only one input to the queue: the InputProcess.

Even though the simpy process of InputProcess does regularly yield events, the Queue never moves away from the yield, indicating that it keeps waiting for an event to be released by the InputProcess, but that process does regularly creates events.

I am not understanding what is wrong here.


Solution

  • here is one thing that is happening.

    in InputProcess(object): the init the line

    self.action = env.process(self.run())
    

    creates a process and assigns it to your action property.

    A process runs until it hits a return statement or gets to the end of the method.

    Your run() method does not have a return, and your loop is infinite, so run() never exits.

    Another point: in the infinite loop of run(), you are creating a customer, creating a timeout() that you yield to, which means you wait till the timeout triggers/finish, then you loop. The customers and the timeouts never get saved anywhere.

    So here is a example of a process generator and a process, no queue needed. This just demos one of many patterns for a simpy simulation

    """
    Simple example of a process genterator
    
    Programmer: Michael R. Gibbs
    """
    
    import simpy
    import random
    
    def a_call_back(env, cust):
        """
        shows how processes can call other stuff
        This is just a normal function and not a simpy process
        """
    
        print(f'{env.now} customer {cust} called a fuction')
    
    
    def cust_proc(env, cust):
        """"
        Simple life of a customer
        """
    
        print(f'{env.now} customer {cust} has started doing stuff')
    
        # does some stuff
        yield env.timeout(random.triangular(1,3,9))
    
        print(f'{env.now} customer {cust} has finished doing stuff')
    
    def gen_cust_processes(env):
        """
        Creates customers and starts their process
        """
    
        id = 0
    
        while True:
            id += 1
            cust = 'Customer: ' + str(id)
    
            # create a process, not no yield here
            proc = env.process(cust_proc(env, cust))
    
            # add call back, need lambda to customise 
            proc.callbacks.append((lambda e, c=cust: a_call_back(env, c)))
    
            # add some time between creates
            yield env.timeout(random.triangular(1,2,5))
    
    # boot it up
    env = simpy.Environment()
    env.process(gen_cust_processes(env))
    
    env.run(100)