Modeling Factory Process with SimPy

64 Views Asked by At

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.

1

There are 1 best solutions below

0
On

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.

import simpy
import itertools

class g:#global constants
    t_sim = 600 #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
        ### OP request ###
        OP = self.OP.request(priority=p_Id) 
        print(f"part {p_Id} OP request at {self.env.now}")
        yield OP #request an operator and wait until one is available
        print(f"part {p_Id} OP seized at {self.env.now}")
        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
        
        ### reqeust Fix01 ###
        Fix01 = self.Fix01.request(priority=p_Id)
        print(f"part {p_Id} Fix01 request at {self.env.now}")
        yield Fix01 #while using the operator resource, request the Fixture, wait until it is avialable to proceed further
        print(f"part {p_Id} Fix01 seized at {self.env.now}")
        yield self.env.timeout(27) #operator loads parts, exits, hits start.
        
        ### op release ###
        yield self.OP.release(OP) #done with operator, release him back to resource pool
        print(f"part {p_Id} OP released at {self.env.now}")
        print("Operator 01 released part ", p_Id, " at ", self.env.now, sep="")
        yield self.env.timeout(0) #take a beat b4 next request
        
        ### request and release R2 ###
        ### R2 is released in the with block so we can keep this one
        with self.R2.request(priority=p_Id) as R2:   
            print(f"part {p_Id} R2 request at {self.env.now}") 
            yield R2 #I need R2 now, request to proceed
            print(f"part {p_Id} R2 seized at {self.env.now}")
            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
            # do not need the release, the with block will do it automatically
            # yield self.R2.release(R2) #weld process done, release the R2 resource back to pool
        print(f"part {p_Id} R2 released at {self.env.now}")

        ### request R1 ###
        R1 = self.R1.request(priority=p_Id)
        print(f"part {p_Id} R1 request at {self.env.now}")
        yield R1 #request R1 to move part out of fixture 01
        print(f"part {p_Id} R1 seized at {self.env.now}")
        yield self.env.timeout(8) #R1 move part out of Fix01
        print("Part ", p_Id, " is leaving Fixture 01 at ", self.env.now, sep="")
        
        ### release Fix01 ###
        yield self.Fix01.release(Fix01) #fixture01 is clear, release back to resource pool
        print(f"part {p_Id} Fix01 released at {self.env.now}")
        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
        
        ### request Fix101 ###
        Fix101 = self.Fix101.request(priority=p_Id)
        print(f"part {p_Id} Fix101 request at {self.env.now}")
        yield Fix101 #ask for Fix101 resource to place part into
        print(f"part {p_Id} Fix101 seized at {self.env.now}")
        print("Part ", p_Id, " enters Fixture 101 at ", self.env.now, sep="")
        
        ### release R1 ###
        yield self.R1.release(R1) #done with R1 robot, send back to resource pool
        print(f"part {p_Id} R1 released at {self.env.now}")
        
        ### request and release R4 ###
        with self.R4.request(priority=p_Id) as R4:
            print(f"part {p_Id} R4 request at {self.env.now}")
            yield R4 #I need R4 to proceed off of Fixture 101 drop off points
            print(f"part {p_Id} R4 seized at {self.env.now}")
            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
        print(f"part {p_Id} R4 released at {self.env.now}")

        ### request R3 ###
        R3 =  self.R3.request(priority=p_Id) 
        print(f"part {p_Id} R3 request at {self.env.now}")
        yield R3 #wait for R3 to be available to move part out of fixture 101 drop off
        print(f"part {p_Id} R3 seized at {self.env.now}")
        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="")
        
        ### release Fix101 ###
        yield self.Fix101.release(Fix101) #release fixture101 drop off back to resource pool
        print(f"part {p_Id} Fix101 released at {self.env.now}")
        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
        
        ### request Fix02 ###
        Fix02 =  self.Fix02.request(priority=p_Id)
        print(f"part {p_Id} Fix02 request at {self.env.now}")
        yield Fix02#request the Fixture 02 bin to place the part into
        print(f"part {p_Id} Fix02 seized at {self.env.now}")
        yield self.env.timeout(0)#take a beat
        print("Part ", p_Id, " enters Fixture 02 at ", self.env.now, sep="")

        ### release R3 ###
        yield self.R3.release(R3)#done with R3, release to resource pool
        print(f"part {p_Id} R3 released at {self.env.now}")

        ### request and release OP ###
        with self.OP.request(priority=p_Id) as OP:
            print(f"part {p_Id} OP request at {self.env.now}")
            yield OP#now need an operator to load more parts
            print(f"part {p_Id} OP seized at {self.env.now}")
            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
        print(f"part {p_Id} OP released at {self.env.now}")

        ### request and release R2
        with self.R2.request(priority=p_Id) as R2:
            print(f"part {p_Id} R2 request at {self.env.now}")
            yield R2#request the R2 to finish the welding on this part
            print(f"part {p_Id} R2 seized at {self.env.now}")
            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
        print(f"part {p_Id} R2 released at {self.env.now}")
        
        ### request and release OP
        with self.OP.request(priority=p_Id) as OP:
            print(f"part {p_Id} OP request at {self.env.now}")
            yield OP#need an operator to remove the part
            print(f"part {p_Id} OP seized at {self.env.now}")
            yield self.env.timeout(2)#operator removes part and restarts cycle
            
            ### release Fix02
            yield self.Fix02.release(Fix02)#done with Fixture 02, release back to pool of resources
            print(f"part {p_Id} Fix02 released at {self.env.now}")
            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
        print(f"part {p_Id} OP released at {self.env.now}")
  
    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()