python: pausing a simulation in simpy - python

I was hoping someone could point me a the right direction as i work through building a queue model in Simpy. The documentation and examples on the Simpy site are amazing and i wanted to see if i could build upon an example they provided.
Essentially, i am simulating a process where a clerk has to review paperwork. The paperwork both arrives and exits his queue (i.e. is serviced) at random time intervals (much like an MM1 queue) with a first-in-first-out service discipline.
The tricky part i am finding is that I would like to model the process such that the clerk has to sleep for some period of time before returning to work. For example, if the simulation runs for "4 weeks", the clerk should sleep for 12 hours per day during that period. I cant seem to get this last caveat to function. Please see my code below, thanks!
import random
import simpy
import matplotlib.pyplot as plt
RANDOM_SEED = 123456 #Random seed
INTERVAL_RECORDS = 30.0 #review records roughly every xx hours?
T_INTER = 28 #generate records roughly every xx hours?
WEEKS = 4
SIM_TIME = WEEKS*7*24*60 #simulation time (hours?)
class record_review:
def __init__(self, env):
self.env = env
self.shift_exchange = env.event()
def record(env, interval, counter, data, t_inter):
"""Source generates records randomly"""
for i in range(1):
"""start off with x=i records in queue"""
c = review(env, 'Record%02d' % i, counter, time_in_queue=30.0)
env.process(c)
t = random.expovariate(1.0 / interval) #set up random generation rate
yield env.timeout(t)
while True:
"""continually generate records throughout simulation"""
yield env.timeout(random.randint(t_inter-2, t_inter+2)) #generate record between +/- 2 of interal
i += 1
c = review(env, 'Record%02d' % i, counter, time_in_queue=30.0)
env.process(c)
t = random.expovariate(1.0 / interval) #random generation rate
yield env.timeout(t)
def review(env, name, counter, time_in_queue):
"""Record arrives, is reviewed and exits."""
arrive = env.now
print('%7.4f %s: Record has entered queue' % (arrive, name))
with counter.request() as req:
yield req
wait = env.now - arrive #total wait time / record
tib = random.expovariate(1.0 / time_in_queue) #time in queue/review rate
yield env.timeout(tib)
data.append(wait) #monitor
print('%7.4f %s: Waited %6.3f' % (env.now, name, wait))
print('%7.4f %s: Finished' % (env.now, name))
def shift_exchange(self, env):
while True:
for i in range(SIM_TIME):
yield env.timeout(60)
self.shift_exchange = env.event()
# Setup and start the simulation
print('Batch Record Review Simulation')
random.seed(RANDOM_SEED)
env = simpy.Environment()
data = []
# Start processes and run
counter = simpy.Resource(env, capacity=1)
env.process(record(env, INTERVAL_RECORDS, counter, data, T_INTER))
env.run(until=SIM_TIME)

Related

How can I ensure that my simulation starts a new "time to failure" countdown only after the repair is complete?

I am creating a simulation of a 2 machine system with a buffer in between with fixed processing speeds, failure rate and maintenance speeds (later I will change this to having some sort of distribution, but for now baby steps first). The steps are as follows:
An unlimited capacity of units is delivered to machine 1.
Machine 1 processes at speed s, fails after f minutes and is repaired
after r minutes. If buffer is full then Machine 1 stops.
Buffer gets filled by Machine 1 and emptied by Machine 2. There is a
max capacity.
Machine 2 processes at min(speed s, capacity buffer) fails after f
minutes and is repaired after r minutes. If buffer is empty then
Machine 2 stops.
Unlimited buffer capacity after Machine 2.
I have gotten quite far with all of the above points accounted for. The only thing is that the failures keep counting the failure time per multiple of the unit I give it and it does not start counting after the machine has been fixed again. So it might break at 7, is fixed at 11 but then breaks at 14 again instead of 11+7=18.
It seems like the failure is just on an infinite loop. Does anyone know how I can change this? My code is as follows:
import simpy
import random
# Machine 1
speed_1 = 2 # Avg. processing time of Machine 1 in minutes
# speed_1_stdev = 0.6 # St. dev. of processing time of Machine 1
MTTF_1 = 10 # Mean time to failure Machine 1
# fail_1 = 1/MTTF_1 # Parameter for exp. distribution
repair_1 = 3 # Time it takes to repair Machine 1
# Machine 2
speed_2 = 3 # Processing time of Machine 2 in minutes
# speed_2_stdev = 0.6 # St. dev. of processing time of Machine 2
MTTF_2 = 7 # Mean time to failure Machine 1
# fail_2 = 1/MTTF_2 # Parameter for exp. distribution
repair_2 = 4 # Time it takes to repair Machine 2
# Simulation time
time = 120 # Sim time in minutes
#---------------------------------------------------------------------
# Class setup for a Machine
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.
Machine has a *name*, a processing speed *speed*, a preceeding buffer *in_q*,
and a proceeding buffer *out_q*.
Next steps:
- Machine produces units at distributed processing speeds.
- A machine fails at fixed intervals and is repaired at a fixed time.
- Failure and repair times are distributed.
"""
def __init__(self, env, name, in_q, out_q, speed, mttf, repair):
self.env = env
self.name = name
self.in_q = in_q
self.out_q = out_q
self.speed = speed
self.mttf = mttf
self.repair = repair
self.broken = False
# Start the producing process
self.process = env.process(self.produce())
# Start the failure process
env.process(self.fail_machine())
def produce(self):
"""
Produce parts as long as the simulation runs.
"""
while True:
part = yield self.in_q.get()
try:
# 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} finish 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')
except simpy.Interrupt:
self.broken = True
yield self.env.timeout(self.repair)
print(f'{self.env.now:.2f} {self.name} is in fixed')
self.broken = False
def fail_machine(self):
"""
The machine is prone to break down every now and then.
"""
while True:
yield self.env.timeout(self.mttf)
print(f'{self.env.now:.2f} {self.name} is in failure.')
if not self.broken:
# Machine only fails if currently working.
self.process.interrupt(self.mttf)
#---------------------------------------------------------------------
# Generating the arrival of parts in the entry buffer to be used by machine 1
def gen_arrivals(env, entry_buffer):
"""
Start the process for each part by putting
the part in the starting 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 = 8) # 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, MTTF_1, repair_1)
machine_2 = Machine(env, 'Machine 2', buffer1, bufferEnd, speed_2, MTTF_2, repair_2)
env.process(gen_arrivals(env, bufferStart))
# Execute
env.run(until = time)
changed the fail_machine method from a infinite loop to generate just the next breakdown. fail_machine still needs to be called from the init startup, but also now needs to be called from the end of the except block that handles the breakdown, restarting the countdown to next failure.
import simpy
import random
# Machine 1
speed_1 = 2 # Avg. processing time of Machine 1 in minutes
# speed_1_stdev = 0.6 # St. dev. of processing time of Machine 1
MTTF_1 = 10 # Mean time to failure Machine 1
# fail_1 = 1/MTTF_1 # Parameter for exp. distribution
repair_1 = 3 # Time it takes to repair Machine 1
# Machine 2
speed_2 = 3 # Processing time of Machine 2 in minutes
# speed_2_stdev = 0.6 # St. dev. of processing time of Machine 2
MTTF_2 = 7 # Mean time to failure Machine 1
# fail_2 = 1/MTTF_2 # Parameter for exp. distribution
repair_2 = 4 # Time it takes to repair Machine 2
# Simulation time
time = 120 # Sim time in minutes
#---------------------------------------------------------------------
# Class setup for a Machine
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.
Machine has a *name*, a processing speed *speed*, a preceeding buffer *in_q*,
and a proceeding buffer *out_q*.
Next steps:
- Machine produces units at distributed processing speeds.
- A machine fails at fixed intervals and is repaired at a fixed time.
- Failure and repair times are distributed.
"""
def __init__(self, env, name, in_q, out_q, speed, mttf, repair):
self.env = env
self.name = name
self.in_q = in_q
self.out_q = out_q
self.speed = speed
self.mttf = mttf
self.repair = repair
self.broken = False
# Start the producing process
self.process = env.process(self.produce())
# Start the failure process
env.process(self.fail_machine())
def produce(self):
"""
Produce parts as long as the simulation runs.
"""
while True:
part = yield self.in_q.get()
try:
# 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} finish 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')
except simpy.Interrupt:
self.broken = True
yield self.env.timeout(self.repair)
print(f'{self.env.now:.2f} {self.name} is in fixed')
self.broken = False
# change to restart next fail here instead of in a infinite loop
self.env.process(self.fail_machine())
def fail_machine(self):
"""
The machine is prone to break down every now and then.
"""
# no longer a loop, relies on breakdow logic to restart
yield self.env.timeout(self.mttf)
print(f'{self.env.now:.2f} {self.name} is in failure.')
if not self.broken:
# Machine only fails if currently working.
self.process.interrupt(self.mttf)
#---------------------------------------------------------------------
# Generating the arrival of parts in the entry buffer to be used by machine 1
def gen_arrivals(env, entry_buffer):
"""
Start the process for each part by putting
the part in the starting 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 = 8) # 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, MTTF_1, repair_1)
machine_2 = Machine(env, 'Machine 2', buffer1, bufferEnd, speed_2, MTTF_2, repair_2)
env.process(gen_arrivals(env, bufferStart))
# Execute
env.run(until = time)

How to set initial queue length in Simpy?

I am just starting to work on an event simulation, and I am having some issues with the queue length. The question is how can I set an initial queue in the following code? For instance, is there a way to set the initial_queue = 200 so that when the simulation begins there are already 200 cars in the queue?
import simpy
num_of_machines = 1
env = simpy.Environment()
bcs = simpy.Resource(env, capacity=num_of_machines)
def process_client(env):
with bcs.request() as req:
i = 0
while True:
i += 1
yield req
print('Car %s starting to charge at %s' % (i, env.now))
yield env.timeout(90)
print('Car %s ending charge at %s' % (i, env.now))
print('Queue size: %s' % len(bcs.queue))
env.process(process_client(env))
env.process(process_client(env))
env.run(until=300)
Which returns:
Car 1 starting to charge at 0
Car 1 ending charge at 90
Queue size: 0
Car 2 starting to charge at 90
Car 2 ending charge at 180
Queue size: 0
Car 3 starting to charge at 180
Car 3 ending charge at 270
Queue size: 0
Car 4 starting to charge at 270
So you are on the right track, but your while loop never ends so the env.process(process_client(env)) never gets executed, but would be a unwanted recursive call anyway.
I broke your code into two parts. The first part models just one car trying to get a charge. The second creates 200 of instances of the first process. Note that we are creating these processes before the model starts. The model does not start until we the env.run(900) line executes. When the model starts, all 200 processes will try to grab the resource and 199 of them will get queue.
here is the code
"""
Simple example of starting a resource with a queue of 200 requests
programmer: Michael R. Gibbs
"""
import simpy
def charge_process(env, res_pool, car_id):
"""
Models a car waiting in a queue for a charger
and charging
"""
with res_pool.request() as req:
yield req
print('Car %s starting to charge at %s' % (car_id, env.now))
yield env.timeout(90)
print('Car %s ending charge at %s' % (car_id, env.now))
print('Queue size: %s' % len(res_pool.queue))
def load_queue(env, res_pool):
"""
create 200 charging processes.
each one will queue up for a charing station
"""
for i in range(1,201):
env.process(charge_process(env, res_pool, i))
# boot
env = simpy.Environment()
res_pool = simpy.Resource(env, capacity=1)
load_queue(env, res_pool)
env.run(900)

simpy Requests piling up in queue

I am simulating meetings in conference rooms and I started with the Bank Renege example as a model.
It is working as intended - this first version just tries to put 5 meetings into 3 rooms in several different hours.
HOWEVER, I notice that the queued requests (those who don't get a conference room) accumulate in the Resource.queue list. Shouldn't they release and disappear? Is this an error on my part? I have tried
req.cancel
simpy.Interrupt
and a few other attempts, to no effect that I can tell.
RANDOM_SEED = 181106
NbrMtgs = 5 # Total number of meetings in a period
INTERVAL = 1.0 # Generate new meetings every x hours
def Schedule(env, NbrMtgs, interval, ResRooms):
#Source generates meetings
while True:
for i in range(NbrMtgs):
h=env.now
m = Meet(env, 'Meeting%02d_%02d' % (h,i), ResRooms, TimeInMtg=MtgLen)
env.process(m)
t = 1
yield env.timeout(t)
def Meet(env, name, ResRooms, TimeInMtg):
#Customer arrives, is served and leaves.
arrive = env.now
print('%7.4f %s: Here I am' % (arrive, name))
with ResRooms.request() as req:
results = yield req | env.timeout(MtgLen)
wait = env.now - arrive
if req in results:
# We got to the ResRooms
print('%7.4f %s: Waited %6.3f' % (env.now, name, wait))
#tib = random.expovariate(1.0 / TimeInMtg)
yield env.timeout(TimeInMtg)
print('%7.4f %s: Finished' % (env.now, name))
else:
# We reneged
#req.cancel() this doesnt clear queue and does give an error at last step
# no notable effect ResRooms.release(req)
#simpy.Interrupt('No rooms') still piling up. no effect
#yield env.timeout(TimeInMtg)
print('%7.4f %s: RENEGED after %6.3f' % (env.now, name, wait))
print("this req=",req) # is something like <Request() object at 0x2449892f908>
print("users",ResRooms.users)
print("queue",ResRooms.queue)
env = simpy.Environment()
# Start processes and run
ResRooms = simpy.Resource(env, capacity=3)
MtgLen = 1
Hours = 8
env.process(Schedule(env, NbrMtgs, INTERVAL, ResRooms))
env.run(until=5)
No that's not an error. If you request the resources the way you did, with the with-statement, all the request that did not get the resource will end up in the queue and wait until the resource is available again. Do you want to cancel those requests?

How to simulate arrivals of clients according to the schedule

I want to simulate customers arrivals at specific time periods (not generated according to statistical distribution). The arrival times are defined in the csv file that I loaded into pandas dataframe df:
df.head()
arrival_time start_service end_service waiting_time service_duration
09:00:20 09:01:00 09:06:00 0.40 5.00
09:01:00 09:02:20 09:04:00 1.20 1.40
This is my current code, but I don't know how to force the entities (clients) to arrive according to the schedule defined in df, e.g. at 09:00:20, then at 09:01:00, etc. I assume that I should also set the starting simulation time in the Environment, but how can I do it? (I don't need the real-time simulation):
import random
import simpy
import pandas as pd
def source(env, df, counter):
for i, row in df.iterrows():
c = client(env, 'Client%02d' % i, counter, row, time_in_bank=row["service_duration"])
env.process(c)
def client(env, name, counter, row, time_in_bank):
arrive = env.now # probably some changes to be done here
print('%s arrived at %7.4f' % (name,arrive))
with counter.request() as req:
results = yield req
wait = env.now - row["waiting_time"]
print('%s waited %6.3f' % (name, wait))
yield env.timeout(time_in_bank)
print('%s exited the office at %7.4f' % (name, env.now))
df = pd.read_csv("arrivals.csv",sep=",",header=0)
env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)
env.process(source(env, df.head(), counter))
env.run()
You need to:
convert datetime objects to timestamps and work with these
Define an initial_time that you pass to Environment()
Define how long one SimPy step is, e.g. 1 step == 1 sec
Example (featuring arrow):
import arrow
import simpy
start = arrow.get('2016-11-05T00:00:00')
env = simpy.Environment(initial_time=start.timestamp)
def proc(env):
print('Proc start at', env.now, arrow.get(env.now))
yield env.timeout(10) # 10 seconds
print('Proc stop at ', env.now, arrow.get(env.now))
p = env.process(proc(env))
env.run(p)
Output:
Proc start at 1478304000 2016-11-05T00:00:00+00:00
Proc stop at 1478304010 2016-11-05T00:00:10+00:00

monitor function name error using simpy

very new to python and trying to use a simpy script I found online to queue times. I am getting a name error when I use "Monitor" - which I thought was part of SimPy. Is there somewhere else I should be importing monitor from?
Thanks in advance for the help!
See below:
#!/usr/bin/env python
from __future__ import generators
import simpy
from multiprocessing import Queue, Process
from random import Random,expovariate,uniform
# MMC.py simulation of an M/M/c/FCFS/inft/infty queue
# 2004 Dec updated and simplified
# $Revision: 1.1.1.5 $ $Author: kgmuller $ $Date: 2006/02/02 13:35:45 $
"""Simulation of an M/M/c queue
Jobs arrive at random into a c-server queue with
exponential service-time distribution. Simulate to
determine the average number and the average time
in the system.
- c = Number of servers = 3
- rate = Arrival rate = 2.0
- stime = mean service time = 1.0
"""
__version__='\nModel: MMC queue'
class Generator(Process):
""" generates Jobs at random """
def execute(self,maxNumber,rate,stime):
##print "%7.4f %s starts"%(now(), self.name)
for i in range(maxNumber):
L = Job("Job "+`i`)
activate(L,L.execute(stime),delay=0)
yield hold,self,grv.expovariate(rate)
class Job(Process):
""" Jobs request a gatekeeper and hold it for an exponential time """
def execute(self,stime):
global NoInSystem
arrTime=now()
self.trace("Hello World")
NoInSystem +=1
m.accum(NoInSystem)
yield request,self,server
self.trace("At last ")
t = jrv.expovariate(1.0/stime)
msT.tally(t)
yield hold,self,t
yield release,self,server
NoInSystem -=1
m.accum(NoInSystem)
mT.tally(now()-arrTime)
self.trace("Geronimo ")
def trace(self,message):
if TRACING:
print "%7.4f %6s %10s (%2d)"%(now(),self.name,message,NoInSystem)
TRACING = 0
print __version__
c = 3
stime = 1.0
rate = 2.0
print "%2d servers, %6.4f arrival rate,%6.4f mean service time"%(c,rate,stime)
grv = Random(333555) # RV for Source
jrv = Random(777999) # RV for Job
NoInSystem = 0
m=Monitor()
mT=Monitor()
msT=Monitor()
server=Resource(c,name='Gatekeeper')
initialize()
g = Generator('gen')
activate(g,g.execute(maxNumber=10,rate=rate,stime=stime),delay=0)
simulate(until=3000.0)
print "Average number in the system is %6.4f"%(m.timeAverage(),)
print "Average time in the system is %6.4f"%(mT.mean(),)
print "Actual average service-time is %6.4f"%(msT.mean(),)
You are currently receiving a name error because Monitor hasn't currently been defined within your script. In order to use the Monitor from within simpy, you will need to either change import simpy to from simpy import Monitor or append simpy.Monitor for the locations that you are currently using the Monitor function.
Ex:
#!/usr/bin/env python
from __future__ import generators
from simpy import Monitor
Or (lines 71-73):
m=simpy.Monitor()
mT=simpy.Monitor()
msT=simpy.Monitor()

Categories

Resources