I've just begun my journey with SimPy. I am attempting to use it to model a factory floor, where parts come out of a bin then are passed through a bunch of processes (I made a diagram for the part flow, a wrinkle in this is that Robot 2 is used at the beginning and then called again to finish the part). I want to create parts then duck march them through the assembly line as the bin in front becomes available.
import simpy
import itertools
class g:#global constants
t_sim = 24*60*60 #maximum simulation set to a day and 1 second
operator_qty = 2 #how many operators? (assume as interchangeable across any operator type process, helps us ask "what is the difference between two running it and one?")
sWeld_t = 2.75 #seconds per spot weld
Adh_t = 1/60 #seconds/mm to apply sealant or adhesive
ArcStud_t = 5.5 #seconds/drawn arc stud weld
ProjStud_t = 10 #seconds/projection stud weld
class cell_model: #represents entire factory cell in which all processes, resources and the modelling environment itself are held
def __init__(self):#initial conditions of cell
self.env = simpy.Environment() #create simpy environment
self._dict=dict()
self.part_counter = 0 #initialize @ part number zero
self.OP = simpy.PriorityResource(self.env,capacity=g.operator_qty)#implement resources, limited number of operators to do operator process, we assume operators can switch
#Declare all robots as resources, process cannot proceed unless proper resource is available
self.R1 = simpy.PriorityResource(self.env,capacity=1) #declare each robot as a resource (like a bin), each robot can only hold 1 part entity at a time, each robot can be in or out of a process (availability changes by request and release commands within a process)
self.R2 = simpy.PriorityResource(self.env,capacity=1)
self.R3 = simpy.PriorityResource(self.env,capacity=1)
self.R4 = simpy.PriorityResource(self.env,capacity=1)
#Declare all Fixture points as resources, a fixture can be interacted with as long as the robot is outside the relevant fixture process
self.Fix01 = simpy.PriorityResource(self.env,capacity=1)#a bin of capacity 01, holds one part at a time, becomes available once released by a process, becomes unavailable when requested by a process
self.Fix02 = simpy.PriorityResource(self.env,capacity=1)
self.Fix101 = simpy.PriorityResource(self.env,capacity=1)
self.env.process(self.part_generator())
def make_part(self,p_Id):#describe the set of processes one single part would experience going through the cell, we will then try to cram the parts throught this process as fast as possible
with self.OP.request(priority=p_Id) as OP:
yield OP #request an operator and wait until one is available
print("Part ", p_Id, " is next available at ", self.env.now, sep="")#report part in time
yield self.env.timeout(29)#operator walks part to tape bench, tapes part, takes part to fixture 01
with self.Fix01.request(priority=p_Id) as Fix01:
yield Fix01 #while using the operator resource, request the Fixture, wait until it is avialable to proceed further
yield self.env.timeout(27) #operator loads parts, exits, hits start.
yield self.OP.release(OP) #done with operator, release him back to resource pool
print("Operator 01 released part ", p_Id, " at ", self.env.now, sep="")
yield self.env.timeout(0) #take a beat b4 next request
with self.R2.request(priority=p_Id) as R2:
yield R2 #I need R2 now, request to proceed
print("R2 moves to Fixture 01 to work on part ", p_Id, " at ", self.env.now, sep="")
yield self.env.timeout(g.sWeld_t*11) #timeout, R2 11 welds @2.75s/weld
yield self.R2.release(R2) #weld process done, release the R2 resource back to pool
with self.R1.request(priority=p_Id) as R1:
yield R1 #request R1 to move part out of fixture 01
yield self.env.timeout(8) #R1 move part out of Fix01
print("Part ", p_Id, " is leaving Fixture 01 at ", self.env.now, sep="")
yield self.Fix01.release(Fix01) #fixture01 is clear, release back to resource pool
yield self.env.timeout(g.sWeld_t*13) #do 13 welds
yield self.env.timeout(0)#take a beat
yield self.env.timeout(g.ArcStud_t*5) #do 5 drawn arc stud
yield self.env.timeout(8) #R1 travels to Fixture 101 drop off
with self.Fix101.request(priority=p_Id) as Fix101:
yield Fix101 #ask for Fix101 resource to place part into
print("Part ", p_Id, " enters Fixture 101 at ", self.env.now, sep="")
yield self.R1.release(R1) #done with R1 robot, send back to resource pool
with self.R4.request(priority=p_Id) as R4:
yield R4 #I need R4 to proceed off of Fixture 101 drop off points
yield self.env.timeout(g.ArcStud_t*6) #R4 does arc_studs within fixture 101 drop off
yield self.R4.release(R4) #done with R4, release back to resource pool
with self.R3.request(priority=p_Id) as R3:
yield R3 #wait for R3 to be available to move part out of fixture 101 drop off
yield self.env.timeout(8) #R3 move part out of fix101, thus freeing it
print("Part ", p_Id, " leaves Fixture 101 at ", self.env.now, sep="")
yield self.Fix101.release(Fix101) #release fixture101 drop off back to resource pool
yield self.env.timeout(g.ProjStud_t*6) #do 6 projection studs
yield self.env.timeout(0) #move to next station
yield self.env.timeout(225*g.Adh_t)#apply 255mm of adhesive/sealant
print("Part ", p_Id, " has adhesive stage finished at ", self.env.now, sep="")
yield self.env.timeout(8)#R3 travels to fixture 02 for drop off
with self.Fix02.request(priority=p_Id) as Fix02:
yield Fix02#request the Fixture 02 bin to place the part into
yield self.env.timeout(0)#take a beat
print("Part ", p_Id, " enters Fixture 02 at ", self.env.now, sep="")
yield self.R3.release(R3)#done with R3, release to resource pool
with self.OP.request(priority=p_Id) as OP:
yield OP#now need an operator to load more parts
yield self.env.timeout(38) #take 38 seconds to do Operator 02 process
yield self.OP.release(OP)#done with operator, send back to resource pool
yield self.env.timeout(0)#take a beat
with self.R2.request(priority=p_Id) as R2:
yield R2#request the R2 to finish the welding on this part
print("R2 moves to Fixture 02 to work on part ", p_Id, " at ", self.env.now, sep="")
yield self.env.timeout(g.sWeld_t*35)#do 35 welds
yield self.R2.release(R2)#done with R2, send back to resource pool
yield self.env.timeout(0)#take a beat
with self.OP.request(priority=p_Id) as OP:
yield OP#need an operator to remove the part
yield self.env.timeout(2)#operator removes part and restarts cycle
yield self.Fix02.release(Fix02)#done with Fixture 02, release back to pool of resources
print("Part ", p_Id, " leaves process at ", self.env.now, sep="")#report part out time
yield self.OP.release(OP)#release operator back to resource pool
yield self.env.timeout(0)#take a beat
def part_generator(self):
for p_Id in itertools.count():
yield self.env.timeout(1)
self.env.process(self.make_part(p_Id))
def run(self,t_sim=g.t_sim): #run method starts up entity generators and tells Simpy to start running the environment for a duration specified in class g:
self.env.run(until=t_sim)
#run the simulation
print("Simulation Starting...")
my_model=cell_model()
my_model.run()
print()
Process Flow Diagram Image of Factory Cell
That's what i want. what I've made is creating a bunch of parallel make_part processes and tries to walk each part through its own private process with limited manufacturing resources. Then I used PriorityResource to make sure the parts are leaving in the order they entered. I don't believe this represents my system, but it's the only way I've seen that's getting me past the "not a generator" requirement.
Are there any examples of how to pass a queue of jobs through a single process out there? Any comments are appreciated.
I reworked your code and fixed the issue of with blocks releasing you resources before you want them released.
This does not fix all the issues. It seems there is still a deadlock somewhere.
Hopes this helps a bit.