SimPy
Simulation in Python
Robert Ramsdell
Great Lakes Dredge & Dock Company
What is SimPy?
- Discreet Event Simulation - The simulation ticks off time between events defined by processes.
- Process Based - simulates the real-world interaction between active processes (machines, customers, messages),
that may or may not compete for limited resources (jobs, clerks, processors).
What is SimPy?
- Provides facilities (Python classes) that model processes and resources
- Provides monitor classes to log and analyze the history of chosen variables during a simulation
Extras
- SimGUI - a basic graphical interface.
- SimPlot - a plotting utility.
- SimulationTrace - to trace the execution of the simulation in detail.
- SimulationStep - Step through the simulation one event at a time.
- SimulationRT - to synchronize the simulation with the wall clock.
Basis of the Simulation
- Processes
- Resources
- Monitors
Basis of the Simulation - Processes
- The active objects of the simulation
- Inherit from the Process Class
- A minimum of one process required for the simulation.
- Requires a single process execution method (PEM) that describes the action of the process.
This method must contain at least one yield statement.
- A process must be activated to start it's operation.
Basis of the Simulation - Resources
- Identical, unchanging units that processes require to continue. May represent bottlenecks or queues.
- Created using the Resource class that keeps track of their usage.
- Processes can request resources, possibly waiting until they are available.
- Have built-in capacity to monitor usage during the simulation.
Basis of the Simulation - Monitors
- Monitor the history of a variable as a list of (value, time) pairs.
- Created using the Monitor class.
- The observe method used to log an observation.
- Provides information such as the total, mean, time average, etc for the variable.
- Provides a histogram of the values of the variable.
Example 1 - Mismatched Dredges
|
- Dredges load at a borrow area, then unload through a discharge pipe onto a beach.
|
Example 1 - Mismatched Dredges
|
- Two Dredges share a single discharge point.
- The Dredges are mismatched, they load at the same rate, but one has 50% more power for discharging,
and 30% faster discharge time.
|
Example 1 - The Process
- Initialize as a subclass of Process
class TSHD(Process):
def __init__(self,name, load, unload):
Process.__init__(self,name=name)
...
PEM includes one or more yield statements
def go(self):
...
while True:
yield hold,self,self.load #Load the hopper
...
yield request,self,riser #Wait on the riser
Example 1 - The Simulation
initialize() - create the event list and set time to 0
initialize()
Define the processes and resources
riser = Resource(1,monitored=True)
Dredges=[TSHD('Dodge Island',250,100),
TSHD('Manhattan Island',250,140)]
Example 1 - The Simulation
activate() the processes so that they start generating events.
for d in Dredges:
activate(d,d.go())
Run the simulation.
simulate(until=1440*2) #Simulate for 2 days
Example 1 - The Output
Dodge Island 350
Manhattan Island 390
250 Dodge Island unloading, waited 0 min
350 Manhattan Island unloading, waited 100 min
...
2300 Manhattan Island unloading, waited 0 min
2440 Dodge Island unloading, waited 40 min
2690 Manhattan Island unloading, waited 0 min
2830 Dodge Island unloading, waited 40 min
Riser in use 60 %
Dodge Island waited 130 min, completed 7
Manhattan Island waited 100 min, completed 7
Total Loads: 14
The Manhattan wait is at the start. After that the Dodge does all the waiting.
Each does the same number of loads.
How SimPy Works
The initialize() function creates an event list
Events in the list are calls to the PEMs at a given time
PEMs do whatever, then post new events to the list using yield statements.
simulate() steps through the event list.
Time passes based on scheduled times for events in the list (i.e. there is no clock).
Advanced Process Control
Requesting resources with priority - jumping the queue
riser = Resource(1,monitored=True,qType=PriorityQ)
...
yield request,self,riser,self.priority
Requesting resources with priority & preemption - taking resources away
riser = Resource(1,monitored=True,qType=PriorityQ,preemptable=1)
...
yield request,self,riser,self.priority
Advanced Process Control
Waiting on events
yield waitevent,self,someevent
or
yield queueevent,self,someevent
someevent is a SimEvent object
Waiting on arbitrary conditions
yield waituntil,self,d.disconnecting
d.disconnecting is a function that returns True or False
Advanced Process Control
Interruptions - One process can interrupt another.
self.interrupt(victim)
The victim, which must be active, returns from it's yield hold prematurely. It can
check to see if it has been interrupted.
if self.interrupted()==True:
#Do something about the interruption
Example 2 - Preemption
Same two dredges
Slower dredge will wait if the other is ready to pumpout
#wait on any higher priority dredges
for d in self.waiton:
if d.status in ['SailLoaded','Turn']:
#The other dredge is coming back, wait until it is
# done pumping out
yield waituntil,self,d.disconnecting
The more powerful dredge can take the riser from the other
#Request the riser - higher priority will pre-empt lower
yield request,self,riser,self.priority
Test the option of teh more productive Dredge taking priority at the riser.
Example 2 - Other changes
More detailed cycle model
cycleorder = ['SailLight','Pump','Turn','SailLoaded','Connect','Discharge',
'Disconnect']
Randomized cycle times and load sizes. Independent random streams and seeds for each dredge.
self.rand=random.Random(seed)
Command-line options to allow changes to process parameters
More sophisticated and flexible simulation.
Example 2 - Results
No waiting or preemption - first-come-first-served
>HopperSim3.py d 120
Manhattan Island Est cycle: 387.0
Dodge Island Est cycle: 354.0
Manhattan Island:
0 wait, 446 loads, 491523 CY total, 386 min Cycle
Dodge Island :
14443 wait, 446 loads, 575450 CY total, 354 min Cycle
Total Loads: 892
Total CY: 1066974 in 120 days
This is essentially the same as Example 1. The more productive dredge does all the waiting.
Example 2 - Results
Manhattan waits if Dodge is near
>HopperSim3.py d 120 wait
Manhattan Island Est cycle: 387.0
Dodge Island Est cycle: 354.0
Manhattan Island :
7536 wait, 427 loads, 470423 CY total, 386 min Cycle
Dodge Island :
6509 wait, 468 loads, 603545 CY total, 354 min Cycle
Total Loads: 895
Total CY: 1073969 in 120 days
Here the Manhattan will wait if the Dodge is almost done loading, or nearing the riser.
Wait time shifts to the Manhattan and 3 loads are gained over 120 days.
Example 2 - Results
Dodge can interrupt the Manhattan at the riser
>HopperSim3.py d 120 wait p
Manhattan Island Est cycle: 387.0
Dodge Island Est cycle: 354.0
Manhattan Island :
14049 wait, 409 loads, 450914 CY total, 387 min Cycle
Dodge Island :
109 wait, 487 loads, 627973 CY total, 354 min Cycle
Total Loads: 896
Total CY: 1078888 in 120 days
Here the Manhattan does all the waiting and 1 more load is gained.
Abusing Processes
As constructors - spitting out other processes at intervals
class Customer(Process):
...
class FrontDoor(Process):
...
def DoorOpens():
yield hold,self,now()+t
c = Customer()
activate(c,c.goinside())
...
As data loggers - Logging data at regular intervals