Search code examples
pythonsimulationsimpy

Is there a way to print values every simulation tick (minute in my case) instead of every time an event happens?


I am creating a simulation of a 2 machine system where the processing speed is hundreds per second, 0.0012-0.0015 to be exact.

In my code I have a print statement telling me what each machine is doing. While running the simulation, I get the print statement for every instance, so about 100 per simulation minute (tick?). Is there a way to get it to only print at the whole tick and not every instance?

My code I have as an example is:

import simpy

# Machine 1
speed_1 = 0.0015          # Avg. processing time of Machine 1 in minutes

# Machine 2
speed_2 = 0.0013          # Processing time of Machine 2 in minutes


# Simulation time
time = 120           # Sim time in minutes

#-------------------------------------------------------------------------------------------
class Machine(object):
    """
    A machine produces units at a fixed processing speed, 
    takes units from a store before and puts units into a store after.
    """
    def __init__(self, env, name, in_q, out_q, speed):
        self.env = env
        self.name = name
        self.in_q = in_q
        self.out_q = out_q
        self.speed = speed

        # Start the producing process
        self.process = env.process(self.produce())
    
    def produce(self):
        """
        Produce parts as long as the simulation runs.
        """
        while True:
            part = yield self.in_q.get()
            # If want to see time {self.env.now:.2f} 
            print(f'{self.name} has got a part')

            yield env.timeout(self.speed)
            if len(self.out_q.items) < self.out_q.capacity:
                print(f'{self.name} finished a part, next buffer has {len(self.out_q.items)} and capacity of {self.out_q.capacity}')
            else:
                print(f'{self.env.now:.2f}  {self.name} output buffer full!!!')

            yield self.out_q.put(part)
            print(f'{self.name} pushed part to next buffer')

#-------------------------------------------------------------------------------------------
# Generating the arrival of parts in the entry buffer to be used by machine 1
def gen_arrivals(env, entry_buffer):
    while True:
        yield env.timeout(random.uniform(0,0.001))
        # print(f'{env.now:.2f} part has arrived')
        part = object() # Too lazy to make a real part class, also isn't necessary

        yield entry_buffer.put(part)
        
#-------------------------------------------------------------------------------------------
# Create environment and start the setup process
env = simpy.Environment()
bufferStart = simpy.Store(env)  # Buffer with unlimited capacity
buffer1 = simpy.Store(env, capacity = 900) # Buffer between machines with limited capacity
bufferEnd = simpy.Store(env)  # Last buffer with unlimited capacity

# The machines __init__ starts the machine process so no env.process() is needed here
machine_1 = Machine(env, 'Machine 1', bufferStart, buffer1, speed_1)
machine_2 = Machine(env, 'Machine 2', buffer1, bufferEnd, speed_2)

env.process(gen_arrivals(env, bufferStart))

# Execute
env.run(until = time)

Solution

  • First off, a model 'tick' can be any unit you want, a year, a day, a second or even 0.0001 of a second. I like to have my steps be a integer to minimize computer float rounding.

    I added a log process that prints the state of the machine at ever 0.01 ticks. To support the log, I added a state property to the machine that I update throughout processing. I also changed the rates and capacities to make a more interesting log

    import simpy
    import random
    
    # Machine 1
    speed_1 = 0.0015          # Avg. processing time of Machine 1 in minutes
    
    # Machine 2
    speed_2 = 0.0035         # Processing time of Machine 2 in minutes
    
    
    # Simulation time
    time = .1           # Sim time in minutes
    
    #-------------------------------------------------------------------------------------------
    class Machine(object):
        """
        A machine produces units at a fixed processing speed, 
        takes units from a store before and puts units into a store after.
        """
        def __init__(self, env, name, in_q, out_q, speed):
            self.env = env
            self.name = name
            self.in_q = in_q
            self.out_q = out_q
            self.speed = speed
            self.state = 'Started'
            self.part_cnt = 0
    
            # Start the producing process
            self.process = env.process(self.produce())
    
            # start the logging
            env.process(self.log())
        
        def produce(self):
            """
            Produce parts as long as the simulation runs.
            """
            while True:
                self.state = 'Waiting for part'
    
                part = yield self.in_q.get()
                # If want to see time {self.env.now:.4f} 
                print(f'{self.name} has got a part')
    
                self.state = 'Processing Part'
    
                yield env.timeout(self.speed)
                if len(self.out_q.items) < self.out_q.capacity:
                    print(f'{self.name} finished a part, next buffer has {len(self.out_q.items)} and capacity of {self.out_q.capacity}')
                else:
                    print(f'{self.env.now:.4f}  {self.name} output buffer full!!!')
    
                self.state = 'Waiting to send part to next queue'
                self.part_cnt += 1
    
                yield self.out_q.put(part)
    
                print(f'{self.name} pushed part to next buffer')
    
        def log(self):
            """
                logs the state of the machine every 0.01 ticks
            """
    
            while True:
    
                print(f'{self.env.now:.4f}  Machine {self.name} has processed {self.part_cnt} parts and is in state {self.state}')
    
                yield self.env.timeout(0.01)
    
    #-------------------------------------------------------------------------------------------
    # Generating the arrival of parts in the entry buffer to be used by machine 1
    def gen_arrivals(env, entry_buffer):
        while True:
            yield env.timeout(random.uniform(0,0.001))
            # print(f'{env.now:.2f} part has arrived')
            part = object() # Too lazy to make a real part class, also isn't necessary
    
            yield entry_buffer.put(part)
            
    #-------------------------------------------------------------------------------------------
    # Create environment and start the setup process
    env = simpy.Environment()
    bufferStart = simpy.Store(env)  # Buffer with unlimited capacity
    buffer1 = simpy.Store(env, capacity = 10) # Buffer between machines with limited capacity
    bufferEnd = simpy.Store(env)  # Last buffer with unlimited capacity
    
    # The machines __init__ starts the machine process so no env.process() is needed here
    machine_1 = Machine(env, 'Machine 1', bufferStart, buffer1, speed_1)
    machine_2 = Machine(env, 'Machine 2', buffer1, bufferEnd, speed_2)
    
    env.process(gen_arrivals(env, bufferStart))
    
    # Execute
    env.run(until = time)