Coverage for C:\checkouts\github\OpenQTSim\openqtsim\simulation.py : 14%

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
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 """
18 def __init__(self, queue, max_arr=100, priority=False, seed=None):
19 """
20 Initialization (the basic time unit is hours)
21 """
23 self.queue = queue
24 self.max_arr = max_arr
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())
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]}
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
52 # activate random seed
53 np.random.seed(seed)
55 # define arrival and service processes
56 if not priority:
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)
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)
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
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')
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}})
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}})
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}})
107 else:
108 pass
109 # Todo: add the option of having priority arrivals?
111 # initiate queue populating process
112 self.env.process(self.queue.populate(self.env, self))
114 def run(self, max_arr=1000):
115 """
116 Run simulation
117 """
119 self.max_arr = max_arr
121 self.env.run()
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 """
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)
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 """
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)
161 def return_log(self):
162 """
163 Return the log in the form of a pandas data frame.
164 """
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])
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])
174 return df_cust, df_sys
176 def get_stats(self):
177 """
178 Post processing of logs to print basic simulation statistics
179 """
181 df_cust, df_sys = self.return_log()
183 value = np.mean(df_cust["TCWQ"]) / np.mean(df_cust["ST"])
184 print('Waiting time over service time: {:.4f}'.format(value))
185 print('')
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))
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('')
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'])
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('')
207 value = df_cust["AT"].iloc[-1]/(len(df_cust["ST"])-1)
208 print('IAT: average inter arrival time: {:.4f}'.format(value))
210 value = np.sum(df_cust["ST"])/(len(df_cust["ST"]))
211 print('ST: average service time: {:.4f}'.format(value))
212 print('')
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 """
219 df_cust, df_sys = self.return_log()
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')
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)