import simpy
import pandas as pd
import numpy as np
import math
SimPy is a process-based discrete-event simulation framework based on standard Python. Its event dispatcher is based on the time of events and the processes it contains. It provides the modeller with components of a simulation model, including processes, for active components like vehicles or customers, and resources, for passive components like servers or counters. SimPy also provides monitors to aid in understanding the model.
Features of SimPy:
To install:
pip intall simpy
The code below demonstrates how you can capture simulation data and a method to then query statistical data about the simulation.
class P:
''' initialize variables '''
parking_duration = 5 # how long is the car parked
trip_duration = 2 # how long is the car driving for
max_sim_time = 105 # how long to run the dimulation for
class Report():
''' capture simulation data '''
def __init__(self):
self.data = []
self.numcars = 0
def updatestats(self):
''' Update statistical counters before calculating summaries
After completion of simulation, updatestats() prior to calculating
statistics.
Returns a Pandas dataframe of delta time, number of active servers,
and number in queue.
If pandas is not installed. It will return a list of
tuples of (elapsed time, active servers, number in queue)
'''
elapseddata = [(self.data[i][0] - self.data[i-1][0], self.data[i][1]) if i > 0 else (self.data[i][0], self.data[i][1]) for i in range(0, len(self.data))]
try:
self.carwashdata = pd.DataFrame(self.data, columns=['time','total_wait_time'])
self.elapseddata = pd.DataFrame(elapseddata, columns=['elapsedtime','total_wait_time'])
return self.carwashdata
except:
return elapseddata
def countcars(self):
''' Number of cars that have gone through the simulation
'''
return self.numcars
def timeAverage(self, elapsedtime, values):
return ((np.sum(elapsedtime * values)) / elapsedtime.sum())
def timeVariance(self, elapsedtime, values):
''' Time weighted unbiased estimator of variance
'''
weightedmu = self.timeAverage(elapsedtime, values)
V = elapsedtime.sum()
weightsumsquared = sum([elapsedtime[i] *
math.pow(values[i] - weightedmu, 2)
for i in range(len(elapsedtime))])
timevariance = weightsumsquared / (V - 1)
return timevariance
def actTimeAverage(self):
''' Time weighted average of number of servers in use
'''
return self.timeAverage(self.elapseddata['elapsedtime'],
self.elapseddata['total_wait_time'])
def waitTimeAverage(self):
''' Time weighted average of number of cars in queue
'''
return self.timeAverage(self.elapseddata['elapsedtime'],
self.elapseddata['total_wait_time'])
def actTimeVariance(self):
''' Time weighted unbiased variance of number of servers in operation
'''
timevariance = self.timeVariance(self.elapseddata['elapsedtime'],
self.elapseddata['total_wait_time'])
return timevariance
def waitTimeVariance(self):
''' Time weighted unbiased variance of number of cars in queue
'''
timevariance = self.timeVariance(self.elapseddata['elapsedtime'],
self.elapseddata['total_wait_time'])
return timevariance
class Car(object):
''' stop and drive the car '''
def __init__(self, env, parking_duration, trip_duration, simdata):
self.env = env
self.parking_duration = parking_duration
self.trip_duration = trip_duration
# this is how we accept the simulation data object
self.simdata = simdata
# start the run process everytime an instance is created.
self.action = env.process(self.park_and_drive())
def park_and_drive(self):
while True:
# get current time
wait_start = self.env.now
# keep track of the number of car entities flowing through the simulation
self.simdata.numcars += 1
print('Start parking at %d' % self.env.now)
yield self.env.timeout(self.parking_duration)
print('Start driving at %d' % self.env.now)
yield self.env.timeout(self.trip_duration)
# collect simulation data
self.simdata.data.append((self.env.now, self.env.now - wait_start))
def model():
''' run simulation '''
# create an environment
env = simpy.Environment()
# capture simulation data
simdata = Report()
# pass the simdata object to the Car class
car = Car(env, P.parking_duration, P.trip_duration, simdata)
# start simulation
env.run(until=P.max_sim_time)
return simdata
d = model()
Start parking at 0 Start driving at 5 Start parking at 7 Start driving at 12 Start parking at 14 Start driving at 19 Start parking at 21 Start driving at 26 Start parking at 28 Start driving at 33 Start parking at 35 Start driving at 40 Start parking at 42 Start driving at 47 Start parking at 49 Start driving at 54 Start parking at 56 Start driving at 61 Start parking at 63 Start driving at 68 Start parking at 70 Start driving at 75 Start parking at 77 Start driving at 82 Start parking at 84 Start driving at 89 Start parking at 91 Start driving at 96 Start parking at 98 Start driving at 103
We need to run the *updatestats* function to have access all the other statistical functions
d.updatestats()
time | total_wait_time | |
---|---|---|
0 | 7 | 7 |
1 | 14 | 7 |
2 | 21 | 7 |
3 | 28 | 7 |
4 | 35 | 7 |
5 | 42 | 7 |
6 | 49 | 7 |
7 | 56 | 7 |
8 | 63 | 7 |
9 | 70 | 7 |
10 | 77 | 7 |
11 | 84 | 7 |
12 | 91 | 7 |
13 | 98 | 7 |
d.actTimeAverage()
7.0
Run a second simulation
d2 = model()
Start parking at 0 Start driving at 5 Start parking at 7 Start driving at 12 Start parking at 14 Start driving at 19 Start parking at 21 Start driving at 26 Start parking at 28 Start driving at 33 Start parking at 35 Start driving at 40 Start parking at 42 Start driving at 47 Start parking at 49 Start driving at 54 Start parking at 56 Start driving at 61 Start parking at 63 Start driving at 68 Start parking at 70 Start driving at 75 Start parking at 77 Start driving at 82 Start parking at 84 Start driving at 89 Start parking at 91 Start driving at 96 Start parking at 98 Start driving at 103
# update second models statistics
d2.updatestats()
time | total_wait_time | |
---|---|---|
0 | 7 | 7 |
1 | 14 | 7 |
2 | 21 | 7 |
3 | 28 | 7 |
4 | 35 | 7 |
5 | 42 | 7 |
6 | 49 | 7 |
7 | 56 | 7 |
8 | 63 | 7 |
9 | 70 | 7 |
10 | 77 | 7 |
11 | 84 | 7 |
12 | 91 | 7 |
13 | 98 | 7 |
You can glue together the two simulation results for analysis
There is not much to see here as the two results are identical
pd.concat([d.carwashdata,d2.carwashdata], axis=1)
time | total_wait_time | time | total_wait_time | |
---|---|---|---|---|
0 | 7 | 7 | 7 | 7 |
1 | 14 | 7 | 14 | 7 |
2 | 21 | 7 | 21 | 7 |
3 | 28 | 7 | 28 | 7 |
4 | 35 | 7 | 35 | 7 |
5 | 42 | 7 | 42 | 7 |
6 | 49 | 7 | 49 | 7 |
7 | 56 | 7 | 56 | 7 |
8 | 63 | 7 | 63 | 7 |
9 | 70 | 7 | 70 | 7 |
10 | 77 | 7 | 77 | 7 |
11 | 84 | 7 | 84 | 7 |
12 | 91 | 7 | 91 | 7 |
13 | 98 | 7 | 98 | 7 |
I originally found the code here:
Since that repo no longer exists, the example seems to be in the link below:
This tutorial was created by HEDARO