├── .gitignore ├── LICENSE ├── MM1.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Robert BASOMINGERA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MM1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Created on April 26, 2018 6 | @author: Robert BASOMINGERA 7 | @course: project 1 in Radio Resources Management 8 | @Ajou University 9 | 10 | This project is developed and tested with Python3.5 using pycharm on Ubuntu 16.04 LTS machine 11 | ''' 12 | 13 | import random 14 | import csv 15 | import copy 16 | import matplotlib.pyplot as plt 17 | 18 | LAMBDAS = [0.03, 0.05, 0.07, 0.09] 19 | JOB_SIZE = 10 20 | 21 | TOTAL_SIMULATION_TIME = 100000 # time for running simulation 22 | DEBUG = False # True for detailed logs 23 | 24 | 25 | # class to create an object that represent a single job 26 | class Job: 27 | def __init__(self, arrival_time, job_id, job_size): 28 | self.job_id = job_id 29 | self.arrival_time = arrival_time 30 | self.service_time = random.expovariate(1 / job_size) 31 | self.service_start_time = 0 32 | self.service_end_time = 0 33 | self.job_delay_time = 0 34 | self.queue_time = 0 35 | self.status = 0 # 0 for created, 1 for queued, 2 for processing, 3 for completed 36 | 37 | def add_and_process_job_queue(self, this_system): 38 | self.service_time = self.service_time 39 | self.service_start_time = max(self.arrival_time, this_system.latest_job_service_ending_time) 40 | self.service_end_time = self.service_start_time + self.service_time 41 | self.queue_time = self.service_start_time - self.arrival_time 42 | self.job_delay_time = self.queue_time + self.service_time 43 | 44 | 45 | class System: 46 | def __init__(self, service_rate): 47 | self.service_rate = service_rate # random.expovariate(self.service_rate) 48 | self.latest_job_service_ending_time = 0 # initially no job 49 | self.queue_list = [] 50 | self.queue_summary_over_time = {} 51 | 52 | def handle_jobs(self, the_new_job): 53 | current_time = the_new_job.arrival_time 54 | self.latest_job_service_ending_time = the_new_job.service_end_time 55 | 56 | new_job_inserted = False 57 | finished_jobs = [] 58 | temp_copy_of_jobs_in_sys = copy.copy(self.queue_list) 59 | 60 | for this_job in temp_copy_of_jobs_in_sys: 61 | if this_job.service_start_time <= current_time and this_job.status < 2: 62 | if DEBUG: 63 | print("Time: " + str(this_job.service_start_time) + "secs \t\tJod Id: " + str( 64 | this_job.job_id) + " Started processing ..... ") 65 | self.queue_summary_over_time[current_time] = len(self.queue_list) 66 | this_job.status = 2 67 | if this_job.service_end_time <= current_time: 68 | this_job.status = 3 69 | if DEBUG: 70 | print("Time: " + str(this_job.service_end_time) + "secs \t\tJod Id: " + str( 71 | this_job.job_id) + " Finished processing , queue size is: " + str(len(self.queue_list) - 1)) 72 | self.queue_list.remove(this_job) 73 | self.queue_summary_over_time[current_time] = len(self.queue_list) 74 | finished_jobs.append(this_job) 75 | else: 76 | continue 77 | 78 | elif this_job.service_end_time <= current_time and this_job.status == 2: 79 | this_job.status = 3 80 | if DEBUG: 81 | print("Time: " + str(this_job.service_end_time) + "secs \t\tJod Id: " + str( 82 | this_job.job_id) + " Finished processing, queue size is: " + str(len(self.queue_list) - 1)) 83 | self.queue_list.remove(this_job) 84 | self.queue_summary_over_time[current_time] = len(self.queue_list) 85 | finished_jobs.append(this_job) 86 | 87 | if not new_job_inserted: 88 | # add current job to the system's jobs 89 | self.queue_list.append(the_new_job) 90 | # update queue summary 91 | self.queue_summary_over_time[current_time] = len(self.queue_list) 92 | 93 | if DEBUG: 94 | print("Time: " + str(current_time) + "secs \t\tJod Id: " + str( 95 | the_new_job.job_id) + " Entered system, system job size is: " + str(len(self.queue_list))) 96 | the_new_job.status = 1 97 | 98 | def finalize_jobs(self): 99 | temp_copy_of_jobs_in_sys_at_end_time = copy.copy(self.queue_list) 100 | current_time = TOTAL_SIMULATION_TIME 101 | 102 | for this_job in temp_copy_of_jobs_in_sys_at_end_time: 103 | if this_job.status == 2: 104 | this_job.status = 3 105 | if DEBUG: 106 | print("Time: " + str(this_job.service_end_time) + "ses \t\tJod Id: " + str( 107 | this_job.job_id) + " Finished processing, queue size is: " + str(len(self.queue_list) - 1)) 108 | self.queue_list.remove(this_job) 109 | self.queue_summary_over_time[this_job.service_end_time] = len(self.queue_list) 110 | if this_job.service_end_time > current_time: 111 | current_time = this_job.service_end_time 112 | elif this_job.status < 2: 113 | if DEBUG: 114 | print("Time: " + str(this_job.service_start_time) + "secs \t\tJod Id: " + str( 115 | this_job.job_id) + " Started processing ..... ") 116 | self.queue_summary_over_time[this_job.service_end_time] = len(self.queue_list) 117 | this_job.status = 2 118 | 119 | this_job.status = 3 120 | if DEBUG: 121 | print("Time: " + str(this_job.service_end_time) + "secs \t\tJod Id: " + str( 122 | this_job.job_id) + " Finished processing , queue size is: " + str(len(self.queue_list) - 1)) 123 | self.queue_list.remove(this_job) 124 | self.queue_summary_over_time[this_job.service_end_time] = len(self.queue_list) 125 | if this_job.service_end_time > current_time: 126 | current_time = this_job.service_end_time 127 | 128 | print("Time: " + str(current_time) + "secs End of last job in the System\nSimulation summary:") 129 | 130 | 131 | class Simulator: 132 | def __init__(self, arrival_rate, service_rate): 133 | self.arrival_rate = arrival_rate 134 | self.system = System(service_rate) 135 | 136 | def run(self, simulation_time): 137 | print("\nTime: 0 sec, Simulation starts for λ=" + str(self.arrival_rate)) 138 | current_time = random.expovariate(self.arrival_rate) 139 | this_jobs = {} # map of id:job 140 | job_id = 1 141 | 142 | while current_time <= simulation_time: 143 | new_job = Job(current_time, job_id, self.system.service_rate) 144 | this_jobs[job_id] = new_job 145 | new_job.add_and_process_job_queue(self.system) 146 | 147 | self.system.handle_jobs(new_job) 148 | current_time += random.expovariate(self.arrival_rate) 149 | job_id += 1 150 | 151 | self.system.finalize_jobs() 152 | print("Total jobs: " + str(len(this_jobs))) 153 | return this_jobs 154 | 155 | 156 | def plot_simulation_delay_time_per_job(jobs, arrival_rate, sumarize): 157 | job_ids = [key for key in jobs] 158 | 159 | simulation_data = [job_ids, [jobs[job_id].job_delay_time for job_id in jobs]] 160 | 161 | simulation_delay_avg = sum(simulation_data[1]) / len(simulation_data[1]) 162 | print("Average delay per job: " + str(simulation_delay_avg)) 163 | simulation_data_delay_averages = [job_ids, [simulation_delay_avg for job_id in jobs]] 164 | 165 | theoretical_data = [job_ids, [1 / ((1 / JOB_SIZE) - arrival_rate) for job_id in jobs]] 166 | 167 | plt.figure(" Figure for lambda=" + str(arrival_rate)) 168 | this_axis = plt.subplot() 169 | this_axis.step(simulation_data[0], simulation_data[1], label='Simulation delay time per job id') 170 | this_axis.step(simulation_data_delay_averages[0], simulation_data_delay_averages[1], label='Simulation E[T]') 171 | this_axis.step(theoretical_data[0], theoretical_data[1], label='Theoretical E[T]') 172 | this_axis.set_xlabel('Job Id') 173 | this_axis.set_ylabel('Delay Time (secs)') 174 | this_axis.legend() 175 | this_axis.set_title( 176 | "Delay time M/M/1 λ=" + str(arrival_rate) + " Simulation time: " + str(TOTAL_SIMULATION_TIME) + " secs") 177 | 178 | sumarize[arrival_rate] = [simulation_delay_avg, 1 / ((1 / JOB_SIZE) - arrival_rate)] 179 | 180 | 181 | if __name__ == '__main__': 182 | summary_results = {} 183 | for this_lambda in LAMBDAS: 184 | simulator = Simulator(this_lambda, JOB_SIZE) 185 | the_jobs = simulator.run(TOTAL_SIMULATION_TIME) 186 | 187 | plot_simulation_delay_time_per_job(the_jobs, this_lambda, summary_results) 188 | 189 | # write data to csv file 190 | if DEBUG: 191 | outDataFileName = "./jobs_" + str(this_lambda) + ".csv" 192 | outfile = open(outDataFileName, "w") 193 | writer = csv.writer(outfile) 194 | header = ['id', 'arrival', 'service time start', 'service time end', 'total delay', 'Service time', 195 | 'queue time'] 196 | writer.writerow(header) 197 | 198 | for job in the_jobs: 199 | job_data = [] 200 | job_data.append(the_jobs[job].job_id) 201 | job_data.append(the_jobs[job].arrival_time) 202 | job_data.append(the_jobs[job].service_start_time) 203 | job_data.append(the_jobs[job].service_end_time) 204 | job_data.append(the_jobs[job].job_delay_time) 205 | job_data.append(the_jobs[job].service_time) 206 | job_data.append(the_jobs[job].queue_time) 207 | writer.writerow(job_data) 208 | outfile.close() 209 | 210 | lamdas = [lamda for lamda in summary_results] 211 | 212 | the_simulation_data = [lamdas, [summary_results[lamda][0] for lamda in lamdas]] 213 | the_theoretical_data = [lamdas, [summary_results[lamda][1] for lamda in lamdas]] 214 | 215 | plt.figure(" Comparison") 216 | axis = plt.subplot() 217 | 218 | plt.plot(the_simulation_data[0], the_simulation_data[1], 'b--') 219 | plt.plot(the_simulation_data[0], the_simulation_data[1], 'bs', label='Simulation delay time') 220 | 221 | plt.plot(the_theoretical_data[0], the_theoretical_data[1], 'g--') 222 | plt.plot(the_theoretical_data[0], the_theoretical_data[1], 'go', label='Theoretical E[T]') 223 | 224 | axis.set_xlabel('Lambda Value') 225 | axis.set_ylabel('Delay Time (secs)') 226 | axis.legend() 227 | axis.set_title("Simulation Vs steady-state: Avg Delay time on M/M/1 " + ", Simulation time: " + str( 228 | TOTAL_SIMULATION_TIME) + "secs") 229 | plt.show() 230 | plt.close() 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M/M/1 queue simulator and steady-state comparison 2 | An M/M/1 queue Python3 simulator that compares the simulation results against the steady state results. 3 | 4 | In queueing theory, M/M/1 is a queue with 1 server, whereby arrivals follow a Poisson process while job service time is an exponential distribution. [Wikipedia](https://en.wikipedia.org/wiki/M/M/1_queue) 5 | ## Getting Started 6 | 7 | This small project has only one file with all codes. All need to simulate is to run the MM1.py file. 8 | 9 | ### Prerequisites 10 | 11 | To run this simulator, your machine needs to have Python 3 installed and matplotlib library. On a Linux machine, this can be installed as follows for example 12 | 13 | ``` 14 | pip install matplotlib 15 | ``` 16 | By default the debug mode is disabled, but once enabled, the file will print logs as of different timestamp event and will generate a CSV file with detailed data. 17 | ## Running the simulator 18 | 19 | The simulator code is in the MM1.py file and can be run from any python IDE such as Pycharm, etc. 20 | Using the terminal, you can use this command once in the directory: 21 | ``` 22 | python3 MM1.py 23 | ``` 24 | 25 | ## Built With 26 | 27 | This project is developed and tested with Python3.5 using Pycharm on Ubuntu 16.04 LTS machine. 28 | 29 | ## License 30 | 31 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 32 | 33 | --------------------------------------------------------------------------------