Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import simpy 

2import pandas as pd 

3import numpy as np 

4import datetime 

5import time 

6from scipy import stats 

7from collections import namedtuple 

8import matplotlib.pyplot as plt 

9 

10 

11class Simulation: 

12 """ 

13 A discrete event simulation that simulates the queue. 

14 - queue is a queue based on the queue class 

15 - seed is a random seed to have retraceable simulations 

16 """ 

17 

18 def __init__(self, queue, max_arr=100, priority=False, seed=None): 

19 """ 

20 Initialization (the basic time unit is hours) 

21 """ 

22 

23 self.queue = queue 

24 self.max_arr = max_arr 

25 

26 # set simulation time and epoch 

27 self.sim_start = datetime.datetime.now() 

28 self.env = simpy.Environment(initial_time=time.mktime(self.sim_start.timetuple())) 

29 self.env.epoch = time.mktime(self.sim_start.timetuple()) 

30 

31 # initialise counters and logs 

32 self.c_s = 0 # people in the system 

33 self.c_q = 0 # people in the queue 

34 self.system_state = { 

35 "t": [0], 

36 "c_s": [0], 

37 "c_q": [0]} 

38 

39 self.customer_nr = 0 

40 self.log = { 

41 "c_id": [], # c_id = customer id 

42 "IAT": [], # IAT = inter arrival time 

43 "ST": [], # ST = service time 

44 "AT": [], # AT = now + IAT 

45 "TSB": [], # TSB = time service begins 

46 "TSE": [], # TSE = time service ends 

47 "TCSS": [], # TCSS = time customer spends in the system 

48 "TCWQ": [], # TCWQ = time customer waits in the queue 

49 "ITS": [], # ITS = idle time of the server 

50 "s_id": []} # s_id = server id 

51 

52 # activate random seed 

53 np.random.seed(seed) 

54 

55 # define arrival and service processes 

56 if not priority: 

57 

58 # --- arrival distribution --- 

59 if self.queue.A.symbol == "M": 

60 # define the average inter arrival time and add distribution with appropriate scaling 

61 aver_IAT = 1 / self.queue.A.arr_rate 

62 self.queue.A.arrival_distribution = stats.expon(scale=aver_IAT) 

63 

64 elif self.queue.A.symbol == "E2": 

65 # define the average inter arrival time and add distribution with appropriate scaling 

66 aver_IAT = 1 / self.queue.A.arr_rate 

67 self.queue.A.arrival_distribution = stats.erlang(2, scale=aver_IAT) 

68 

69 elif self.queue.A.symbol == "D": 

70 # the deterministic type expects arr_rate to contain a dataframe with columns ["name","IAT","AT"] 

71 self.queue.A.arrival_distribution = self.queue.A.arr_rate 

72 

73 # --- service distribution --- 

74 self.env.servers = simpy.FilterStore(self.env, capacity=self.queue.c) 

75 self.env.servers.items = [] # to be filled in the next steps depending on S.symbol 

76 self.env.server_info = {} # to be filled in the next steps depending on S.symbol 

77 Server = namedtuple('Server', 'service_distribution, last_active, id') 

78 

79 if self.queue.S.symbol == "M": 

80 # define the average service time and add distribution with appropriate scaling for each server 

81 aver_ST = 1 / self.queue.S.srv_rate 

82 for i in range(1, self.queue.c + 1): 

83 self.env.servers.items.append(Server(stats.expon(scale=aver_ST), self.env.now, i)) 

84 self.env.server_info.update({i: {'last_active': self.env.now}}) 

85 

86 elif self.queue.S.symbol == "E2": 

87 # define the average service time and add distribution with appropriate scaling for each server 

88 aver_ST = 1 / self.queue.S.srv_rate 

89 for i in range(1, self.queue.c + 1): 

90 self.env.servers.items.append(Server(stats.erlang(2, scale=aver_ST), self.env.now, i)) 

91 self.env.server_info.update({i: {'last_active': self.env.now}}) 

92 

93 elif self.queue.A.symbol == "D": 

94 if self.queue.c == 1: 

95 # for 1 server the deterministic type expects srv_rate to contain a dataframe 

96 # with columns: ["name","ST"] 

97 self.env.servers.items.append(Server(self.queue.S.srv_rate, self.env.now, 1)) 

98 self.env.server_info.update({1: {'last_active': self.env.now}}) 

99 else: 

100 # for n servers the deterministic type expects srv_rate to contain a dataframe 

101 # with columns: ["name","ST","s_id"] 

102 for i in range(1, self.queue.c + 1): 

103 self.env.servers.items.append(Server(self.queue.S.srv_rate[self.queue.S.srv_rate["s_id"] == i], 

104 self.env.now, i)) 

105 self.env.server_info.update({i: {'last_active': self.env.now}}) 

106 

107 else: 

108 pass 

109 # Todo: add the option of having priority arrivals? 

110 

111 # initiate queue populating process 

112 self.env.process(self.queue.populate(self.env, self)) 

113 

114 def run(self, max_arr=1000): 

115 """ 

116 Run simulation 

117 """ 

118 

119 self.max_arr = max_arr 

120 

121 self.env.run() 

122 

123 def log_customer_state(self, customer_id, IAT, AT, ST, TSB, TSE, ITS, s_id): 

124 """ 

125 # the following items are logged per customer that enters the system: 

126 # c = customer id 

127 # IAT = inter arrival time 

128 # ST = service time 

129 # AT = arrival time 

130 # TSB = time service begins 

131 # TSE = time service ends 

132 # TCSS = time customer spends in the system 

133 # TCWQ = time customer waits in the queue 

134 # ITS = idle time of the server 

135 # s_id = id of server assigned to customer 

136 """ 

137 

138 self.log["c_id"].append(customer_id) 

139 self.log["IAT"].append(IAT) 

140 self.log["ST"].append(ST) 

141 self.log["AT"].append(AT) 

142 self.log["TSB"].append(TSB) 

143 self.log["TSE"].append(TSE) 

144 self.log["TCWQ"].append(TSB - AT) 

145 self.log["TCSS"].append(TSE - AT) 

146 self.log["ITS"].append(ITS) 

147 self.log["s_id"].append(s_id) 

148 

149 def log_system_state(self, t, c_s, c_q): 

150 """ 

151 # the following items are logged for the state of the system: 

152 # t = time (from start of simulation) 

153 # c_s = number of customers in the system 

154 # c_q = number of customers in the queue 

155 """ 

156 

157 self.system_state["t"].append(t) 

158 self.system_state["c_s"].append(c_s) 

159 self.system_state["c_q"].append(c_q) 

160 

161 def return_log(self): 

162 """ 

163 Return the log in the form of a pandas data frame. 

164 """ 

165 

166 # convert self.log to dataframe 

167 df_cust = pd.DataFrame.from_dict(self.log) 

168 df_cust = df_cust.sort_values(by=['AT'], ascending=[True]) 

169 

170 # convert self.system_state to dataframe 

171 df_sys = pd.DataFrame.from_dict(self.system_state) 

172 df_sys = df_sys.sort_values(by=['t'], ascending=[True]) 

173 

174 return df_cust, df_sys 

175 

176 def get_stats(self): 

177 """ 

178 Post processing of logs to print basic simulation statistics 

179 """ 

180 

181 df_cust, df_sys = self.return_log() 

182 

183 value = np.mean(df_cust["TCWQ"]) / np.mean(df_cust["ST"]) 

184 print('Waiting time over service time: {:.4f}'.format(value)) 

185 print('') 

186 

187 value = (df_cust["TSE"].iloc[-1] - np.sum(df_cust["ITS"])) / df_cust["TSE"].iloc[-1] 

188 print('Rho_system: system utilisation: {:.4f}'.format(value)) 

189 value = (df_cust["TSE"].iloc[-1] - (np.sum(df_cust["ITS"])/self.queue.c)) / df_cust["TSE"].iloc[-1] 

190 print('Rho_server: server utilisation: {:.4f}'.format(value)) 

191 

192 value = np.sum(df_cust["ITS"]) / df_cust["TSE"].iloc[-1] 

193 print('P_0: probability nobody in the system: {:.4f}'.format(value)) 

194 print('') 

195 

196 value = np.mean(df_sys['c_s']) 

197 print('L_s: average nr of customers in the system: {}'.format(value)) 

198 value = np.mean(df_sys['c_q']) 

199 

200 print('L_q: average nr of customers in the queue: {}'.format(value)) 

201 value = np.mean(df_cust["TCSS"]) 

202 print('W_s: the long term average time spent in the system: {:.4f}'.format(value)) 

203 value = np.mean(df_cust["TCWQ"]) 

204 print('W_q: the long term average time spent in the queue: {:.4f}'.format(value)) 

205 print('') 

206 

207 value = df_cust["AT"].iloc[-1]/(len(df_cust["ST"])-1) 

208 print('IAT: average inter arrival time: {:.4f}'.format(value)) 

209 

210 value = np.sum(df_cust["ST"])/(len(df_cust["ST"])) 

211 print('ST: average service time: {:.4f}'.format(value)) 

212 print('') 

213 

214 def plot_system_state(self, fontsize=20): 

215 """ 

216 Plot number of customers in the system and in the queue as a function of time 

217 """ 

218 

219 df_cust, df_sys = self.return_log() 

220 

221 fig, ax = plt.subplots(figsize=(14, 5)) 

222 ax.plot(df_sys['t'].values, df_sys['c_s'].values, '-bo', markersize=.1, label='c_s') 

223 ax.plot(df_sys['t'].values, df_sys['c_q'].values, '-ro', markersize=.1, label='c_q') 

224 

225 ax.set_xlabel('Time [hours]', fontsize=fontsize) 

226 ax.set_ylabel('nr of customers', fontsize=fontsize) 

227 ax.set_title('System state: {}'.format(self.queue.kendall_notation), fontsize=fontsize) 

228 ax.legend(loc='upper right', fontsize=fontsize) 

229