├── .gitignore ├── LICENSE ├── README.md ├── documents └── poster-slicesim.pdf ├── examples ├── istanbul-kapalicarsi.yml └── output_n5000_t3600.png ├── requirements.txt └── slicesim ├── BaseStation.py ├── Client.py ├── Container.py ├── Coverage.py ├── Distributor.py ├── Graph.py ├── Slice.py ├── Stats.py ├── __init__.py ├── __main__.py ├── example-input.yml └── utils.py /.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 | 106 | # VSCode 107 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 dilmac 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SliceSim: A Simulation Suite for Network Slicing in 5G Networks 2 | **Abdurrahman Dilmaç** (abdurrahman.d _at_ icloud _dot_ com) 3 | **Muhammed Emin Güre** (memingure _at_ gmail _dot_ com) 4 | *Project Advisor:* **Prof. Tuna Tuğcu** 5 | *** 6 | ### Introduction 7 | 5G widely defines network slicing concept which aims to provide different and separate dedicated logical networks that can be customized to respective services. All slices under a cloud infrastructure are put together with their different requirements, e.g. bandwidth, latency 8 | 9 | The purpose of this project is to provide a simulation suite for a network consisting of base stations and clients that possible scenarios of 5G can fit into and make analysis of different concepts easier. 10 | 11 | ### Approach 12 | - Discrete event simulation 13 | - Using **Python 3.7, Simpy, Matplotlib, KDTree** 14 | - **YAML** for reading input configurations 15 | - Asynchronous programming 16 | - Definitions: 17 | - **Clients:** Simulation consumers. Generates consume requests by given distribution parameters. 18 | - **Slices of Base Stations:** Simulation resources. 19 | 20 | ### Input 21 | 22 | #### Settings 23 | ```yaml 24 | settings: 25 | simulation_time: 100 # in seconds 26 | num_clients: 100 27 | limit_closest_base_stations: 5 # how many base stations stored in a client instance 28 | statistics_params: 29 | warmup_ratio: 0.05 # statistic collection will start from this point 30 | cooldown_ratio: 0.05 # statistic collection will end at this point 31 | # Statistic collection will be in this area of the coordinate system 32 | x: 33 | min: 0 34 | max: 1980 35 | y: 36 | min: 0 37 | max: 1980 38 | logging: False # saving logs to a file 39 | log_file: output.txt # name of the log file 40 | plotting_params: 41 | plotting: True # plot the statistics after execution 42 | plot_save: True # save plot as image 43 | plot_show: False # show plot after execution 44 | plot_file: output.png # name of the plot image 45 | plot_file_dpi: 1000 # dots per inch for plot image 46 | scatter_size: 15 47 | ``` 48 | 49 | #### Slices 50 | ```yaml 51 | slices: 52 | slice_name: # name of the slice 53 | delay_tolerance: 10 54 | qos_class: 5 55 | bandwidth_guaranteed: 0 # in bps 56 | bandwidth_max: 100000000 # in bps 57 | client_weight: 0.39 # [0,1] - ratio of the clients subscribed to this slice in the system. All weights for slices must be 1 in total 58 | threshold: 0 # for dynamic slicing (future work) 59 | # defines the bit usage pattern for client subscribed to this slice 60 | usage_pattern: 61 | distribution: randint # distribution name 62 | params: # distribution parameters 63 | - 4000000 # min value for this example 64 | - 800000000 # max value for this example 65 | slice_name_2: 66 | ... 67 | ``` 68 | 69 | #### Base Stations 70 | ```yaml 71 | base_stations: 72 | - x: 182 # in meters 73 | y: 1414 # in meters 74 | capacity_bandwidth: 20000000000 # in bps 75 | coverage: 224 # in meters 76 | # ratios of the slices in this specific base station 77 | # must be 1 in total for each base station 78 | ratios: 79 | slice_name: 0.20 # [0,1] 80 | slice_name_2: 0.59 # [0,1] 81 | ... 82 | - ... 83 | ``` 84 | 85 | #### Mobility Patterns 86 | ```yaml 87 | mobility_patterns: 88 | mobility_pattern_1: 89 | distribution: normal # distribution name 90 | params: # distribution parameters 91 | - 0 # mean value for this example 92 | - 7 # standard deviation name for this example 93 | client_weight: 0.10 # [0,1] - ratio of the clients assigned to this pattern in the system. All weights must be 1 in total 94 | mobility_pattern_2: 95 | ... 96 | ``` 97 | 98 | #### Client population 99 | ```yaml 100 | clients: 101 | location: # populates the area with the given distributions 102 | x: 103 | distribution: randint 104 | params: 105 | - 0 106 | - 1980 107 | y: 108 | distribution: randint 109 | params: 110 | - 0 111 | - 1980 112 | usage_frequency: # defines the usage generation intervals of clients 113 | distribution: randint 114 | params: 115 | - 0 116 | - 100000 117 | divide_scale: 1000000 # scaling factor 118 | ``` 119 | 120 | ### Usage 121 | Python 3 is required with required dependencies listed in `requirements.txt` installed. Please do not send us email if you haven't done this. 122 | 123 | ```bash 124 | python -m slicesim 125 | ``` 126 | 127 | ### Example Output 128 | ![Example output for 5000 client in 3600s](https://github.com/cerob/slicesim/blob/master/examples/output_n5000_t3600.png) 129 | 130 | ### Conclusion 131 | - Increasing number of clients increases used bandwidth, 132 | and yet the simulation showed that block ratio also 133 | elevates for this specific configurations. 134 | 135 | This simulation tool can be used for such scenarios as well: 136 | 137 | - Testing the effect of different dynamic slicing algorithms 138 | on block and handover ratios. 139 | - Analyzing various mobility patterns of clients using 140 | different statistical distributions. 141 | - Observing the effect of usage frequency of clients and the 142 | effect of clients those are distributed unequally. 143 | - Various Proof of Concepts like common base stations for 144 | multiple service providers. 145 | 146 | ### Future Work 147 | - Customizable shapes for base station coverages 148 | - Improvements of the software performance 149 | - Dynamic slicing mechanism 150 | - Generation of more test configurations 151 | - Video output of a running simulation 152 | 153 | ### References 154 | 1. 5GPPP Architecture Working Group. View on 5G Architecture. Version 2.0, December 2017 155 | 2. CellMapper - https://www.cellmapper.net (10.05.2019) 156 | 3. FatihMunicipalityGeographicInformationSystem- 157 | https://gis.fatih.bel.tr/webgis (13.05.2019) 158 | 4. https://venturebeat.com/2018/12/12/decoding-5g-a-cheat- 159 | sheet-for-next-gen-cellular-concepts-and-jargon/ (17.03.2019) 160 | -------------------------------------------------------------------------------- /documents/poster-slicesim.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerob/slicesim/af3ac84ed7f5f4430c521d70c362caf21aa720f4/documents/poster-slicesim.pdf -------------------------------------------------------------------------------- /examples/istanbul-kapalicarsi.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | simulation_time: 100 3 | num_clients: 100 4 | limit_closest_base_stations: 5 5 | statistics_params: 6 | warmup_ratio: 0.05 7 | cooldown_ratio: 0.05 8 | x: 9 | min: 0 10 | max: 1980 11 | y: 12 | min: 0 13 | max: 1980 14 | logging: False 15 | log_file: output.txt 16 | plotting_params: 17 | plotting: True 18 | plot_save: True 19 | plot_show: False 20 | plot_file: output.png 21 | plot_file_dpi: 1000 22 | scatter_size: 15 23 | slices: 24 | x_eMBB: 25 | delay_tolerance: 10 26 | qos_class: 5 27 | bandwidth_guaranteed: 0 28 | bandwidth_max: 100000000 29 | client_weight: 0.39 30 | threshold: 0 31 | usage_pattern: 32 | distribution: randint 33 | params: 34 | - 4000000 35 | - 800000000 36 | x_mMTC: 37 | delay_tolerance: 10 38 | qos_class: 2 39 | bandwidth_guaranteed: 1000000 40 | bandwidth_max: 10000000 41 | client_weight: 0.10 42 | threshold: 0 43 | usage_pattern: 44 | distribution: randint 45 | params: 46 | - 800000 47 | - 8000000 48 | x_URLLC: 49 | delay_tolerance: 10 50 | qos_class: 1 51 | bandwidth_guaranteed: 5000000 52 | bandwidth_max: 10000000 53 | client_weight: 0.02 54 | threshold: 0 55 | usage_pattern: 56 | distribution: randint 57 | params: 58 | - 800 59 | - 8000000 60 | x_voice: 61 | delay_tolerance: 10 62 | qos_class: 3 63 | bandwidth_guaranteed: 500000 64 | bandwidth_max: 1000000 65 | client_weight: 0.06 66 | threshold: 0 67 | usage_pattern: 68 | distribution: randint 69 | params: 70 | - 4000000 71 | - 8000000 72 | y_eMBB: 73 | delay_tolerance: 10 74 | qos_class: 5 75 | bandwidth_guaranteed: 0 76 | bandwidth_max: 100000000 77 | client_weight: 0.30 78 | threshold: 0 79 | usage_pattern: 80 | distribution: randint 81 | params: 82 | - 2000000 83 | - 600000000 84 | y_eMBB_p: 85 | delay_tolerance: 10 86 | qos_class: 4 87 | bandwidth_guaranteed: 100000000 88 | bandwidth_max: 1000000000 89 | client_weight: 0.05 90 | threshold: 0 91 | usage_pattern: 92 | distribution: randint 93 | params: 94 | - 10000000 95 | - 1000000000 96 | y_voice: 97 | delay_tolerance: 10 98 | qos_class: 3 99 | bandwidth_guaranteed: 500000 100 | bandwidth_max: 1000000 101 | client_weight: 0.08 102 | threshold: 0 103 | usage_pattern: 104 | distribution: randint 105 | params: 106 | - 4000000 107 | - 8000000 108 | base_stations: 109 | - capacity_bandwidth: 20000000000 110 | coverage: 224 111 | ratios: 112 | x_URLLC: 0.01 113 | x_eMBB: 0.59 114 | x_mMTC: 0.05 115 | x_voice: 0.05 116 | y_eMBB: 0.2 117 | y_eMBB_p: 0.05 118 | y_voice: 0.05 119 | x: 182 120 | y: 1414 121 | - capacity_bandwidth: 20000000000 122 | coverage: 250 123 | ratios: 124 | x_URLLC: 0.01 125 | x_eMBB: 0.37 126 | x_mMTC: 0.05 127 | x_voice: 0.06 128 | y_eMBB: 0.4 129 | y_eMBB_p: 0.05 130 | y_voice: 0.05 131 | x: 556 132 | y: 1262 133 | - capacity_bandwidth: 25000000000 134 | coverage: 276 135 | ratios: 136 | x_URLLC: 0.01 137 | x_eMBB: 0.38 138 | x_mMTC: 0.05 139 | x_voice: 0.05 140 | y_eMBB: 0.4 141 | y_eMBB_p: 0.05 142 | y_voice: 0.06 143 | x: 514 144 | y: 766 145 | - capacity_bandwidth: 30000000000 146 | coverage: 316 147 | ratios: 148 | x_URLLC: 0.01 149 | x_eMBB: 0.45 150 | x_mMTC: 0.05 151 | x_voice: 0.04 152 | y_eMBB: 0.36 153 | y_eMBB_p: 0.05 154 | y_voice: 0.04 155 | x: 64 156 | y: 510 157 | - capacity_bandwidth: 30000000000 158 | coverage: 384 159 | ratios: 160 | x_URLLC: 0.05 161 | x_eMBB: 0.2 162 | x_mMTC: 0.1 163 | x_voice: 0.15 164 | y_eMBB: 0.25 165 | y_eMBB_p: 0.1 166 | y_voice: 0.15 167 | x: 126 168 | y: 1016 169 | - capacity_bandwidth: 25000000000 170 | coverage: 348 171 | ratios: 172 | x_URLLC: 0.01 173 | x_eMBB: 0.48 174 | x_mMTC: 0.06 175 | x_voice: 0.03 176 | y_eMBB: 0.36 177 | y_eMBB_p: 0.01 178 | y_voice: 0.05 179 | x: 1296 180 | y: 980 181 | - capacity_bandwidth: 25000000000 182 | coverage: 334 183 | ratios: 184 | x_URLLC: 0.01 185 | x_eMBB: 0.4 186 | x_mMTC: 0.04 187 | x_voice: 0.04 188 | y_eMBB: 0.38 189 | y_eMBB_p: 0.1 190 | y_voice: 0.03 191 | x: 544 192 | y: 1714 193 | - capacity_bandwidth: 30000000000 194 | coverage: 316 195 | ratios: 196 | x_URLLC: 0.01 197 | x_eMBB: 0.36 198 | x_mMTC: 0.04 199 | x_voice: 0.09 200 | y_eMBB: 0.3 201 | y_eMBB_p: 0.12 202 | y_voice: 0.08 203 | x: 996 204 | y: 1822 205 | - capacity_bandwidth: 80000000000 206 | coverage: 418 207 | ratios: 208 | x_URLLC: 0.01 209 | x_eMBB: 0.47 210 | x_mMTC: 0.03 211 | x_voice: 0.07 212 | y_eMBB: 0.35 213 | y_eMBB_p: 0.03 214 | y_voice: 0.04 215 | x: 1568 216 | y: 1608 217 | - capacity_bandwidth: 35000000000 218 | coverage: 356 219 | ratios: 220 | x_URLLC: 0.01 221 | x_eMBB: 0.4 222 | x_mMTC: 0.07 223 | x_voice: 0.06 224 | y_eMBB: 0.35 225 | y_eMBB_p: 0.06 226 | y_voice: 0.05 227 | x: 980 228 | y: 1370 229 | - capacity_bandwidth: 35000000000 230 | coverage: 206 231 | ratios: 232 | x_URLLC: 0.02 233 | x_eMBB: 0.2 234 | x_mMTC: 0.2 235 | x_voice: 0.15 236 | y_eMBB: 0.18 237 | y_eMBB_p: 0.15 238 | y_voice: 0.1 239 | x: 792 240 | y: 988 241 | - capacity_bandwidth: 40000000000 242 | coverage: 392 243 | ratios: 244 | x_URLLC: 0.01 245 | x_eMBB: 0.42 246 | x_mMTC: 0.08 247 | x_voice: 0.05 248 | y_eMBB: 0.35 249 | y_eMBB_p: 0.04 250 | y_voice: 0.05 251 | x: 878 252 | y: 560 253 | - capacity_bandwidth: 75000000000 254 | coverage: 406 255 | ratios: 256 | x_URLLC: 0.01 257 | x_eMBB: 0.42 258 | x_mMTC: 0.04 259 | x_voice: 0.04 260 | y_eMBB: 0.4 261 | y_eMBB_p: 0.05 262 | y_voice: 0.04 263 | x: 372 264 | y: 180 265 | - capacity_bandwidth: 50000000000 266 | coverage: 384 267 | ratios: 268 | x_URLLC: 0.01 269 | x_eMBB: 0.4 270 | x_mMTC: 0.06 271 | x_voice: 0.03 272 | y_eMBB: 0.3 273 | y_eMBB_p: 0.15 274 | y_voice: 0.05 275 | x: 1066 276 | y: 94 277 | - capacity_bandwidth: 100000000000 278 | coverage: 464 279 | ratios: 280 | x_URLLC: 0.01 281 | x_eMBB: 0.4 282 | x_mMTC: 0.04 283 | x_voice: 0.05 284 | y_eMBB: 0.35 285 | y_eMBB_p: 0.1 286 | y_voice: 0.05 287 | x: 1864 288 | y: 962 289 | - capacity_bandwidth: 30000000000 290 | coverage: 302 291 | ratios: 292 | x_URLLC: 0.01 293 | x_eMBB: 0.44 294 | x_mMTC: 0.05 295 | x_voice: 0.05 296 | y_eMBB: 0.3 297 | y_eMBB_p: 0.1 298 | y_voice: 0.05 299 | x: 1442 300 | y: 476 301 | - capacity_bandwidth: 35000000000 302 | coverage: 330 303 | ratios: 304 | x_URLLC: 0.01 305 | x_eMBB: 0.44 306 | x_mMTC: 0.07 307 | x_voice: 0.07 308 | y_eMBB: 0.3 309 | y_eMBB_p: 0.05 310 | y_voice: 0.06 311 | x: 1862 312 | y: 296 313 | - capacity_bandwidth: 20000000000 314 | coverage: 242 315 | ratios: 316 | x_URLLC: 0.01 317 | x_eMBB: 0.43 318 | x_mMTC: 0.03 319 | x_voice: 0.07 320 | y_eMBB: 0.35 321 | y_eMBB_p: 0.5 322 | y_voice: 0.6 323 | x: 1538 324 | y: 26 325 | - capacity_bandwidth: 10000000000 326 | coverage: 152 327 | ratios: 328 | x_URLLC: 0.05 329 | x_eMBB: 0.25 330 | x_mMTC: 0.2 331 | x_voice: 0.1 332 | y_eMBB: 0.25 333 | y_eMBB_p: 0.05 334 | y_voice: 0.1 335 | x: 1448 336 | y: 1456 337 | - capacity_bandwidth: 50 338 | coverage: 368 339 | ratios: 340 | x_URLLC: 0.01 341 | x_eMBB: 0.4 342 | x_mMTC: 0.03 343 | x_voice: 0.1 344 | y_eMBB: 0.38 345 | y_eMBB_p: 0.03 346 | y_voice: 0.05 347 | x: 44 348 | y: 1916 349 | mobility_patterns: 350 | car: 351 | distribution: normal 352 | params: 353 | - 0 354 | - 7 355 | client_weight: 0.10 356 | walk: 357 | distribution: randint 358 | params: 359 | - -1 360 | - 1 361 | client_weight: 0.40 362 | stationary: 363 | distribution: normal 364 | params: 365 | - 0 366 | - 0.1 367 | client_weight: 0.20 368 | tram: 369 | distribution: randint 370 | params: 371 | - -4 372 | - 4 373 | client_weight: 0.10 374 | slackperson: 375 | distribution: randint 376 | params: 377 | - 0 378 | - 1 379 | client_weight: 0.20 380 | clients: 381 | location: 382 | x: 383 | distribution: randint 384 | params: 385 | - 0 386 | - 1980 387 | y: 388 | distribution: randint 389 | params: 390 | - 0 391 | - 1980 392 | usage_frequency: 393 | distribution: randint 394 | params: 395 | - 0 396 | - 100000 397 | divide_scale: 1000000 398 | -------------------------------------------------------------------------------- /examples/output_n5000_t3600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerob/slicesim/af3ac84ed7f5f4430c521d70c362caf21aa720f4/examples/output_n5000_t3600.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | kiwisolver==1.1.0 3 | matplotlib==3.0.3 4 | numpy==1.16.3 5 | Pillow==6.2.0 6 | pyparsing==2.4.0 7 | python-dateutil==2.8.0 8 | PyYAML==5.1 9 | randomcolor==0.4.4.5 10 | scikit-learn==0.20.3 11 | scipy==1.2.1 12 | simpy==3.0.11 13 | six==1.12.0 14 | sklearn==0.0 15 | -------------------------------------------------------------------------------- /slicesim/BaseStation.py: -------------------------------------------------------------------------------- 1 | class BaseStation: 2 | def __init__(self, pk, coverage, capacity_bandwidth, slices=None): 3 | self.pk = pk 4 | self.coverage = coverage 5 | self.capacity_bandwidth = capacity_bandwidth 6 | self.slices = slices 7 | print(self) 8 | 9 | def __str__(self): 10 | return f'BS_{self.pk:<2}\t cov:{self.coverage}\t with cap {self.capacity_bandwidth:<5}' 11 | 12 | -------------------------------------------------------------------------------- /slicesim/Client.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import random 3 | 4 | from .utils import distance, KDTree 5 | 6 | 7 | class Client: 8 | def __init__(self, pk, env, x, y, mobility_pattern, 9 | usage_freq, 10 | subscribed_slice_index, stat_collector, 11 | base_station=None): 12 | self.pk = pk 13 | self.env = env 14 | self.x = x 15 | self.y = y 16 | self.mobility_pattern = mobility_pattern 17 | self.usage_freq = usage_freq 18 | self.base_station = base_station 19 | self.stat_collector = stat_collector 20 | self.subscribed_slice_index = subscribed_slice_index 21 | self.usage_remaining = 0 22 | self.last_usage = 0 23 | self.closest_base_stations = [] 24 | self.connected = False 25 | 26 | # Stats 27 | self.total_connected_time = 0 28 | self.total_unconnected_time = 0 29 | self.total_request_count = 0 30 | self.total_consume_time = 0 31 | self.total_usage = 0 32 | 33 | self.action = env.process(self.iter()) 34 | # print(self.usage_freq) 35 | 36 | def iter(self): 37 | ''' 38 | There are four steps in a cycle: 39 | 1- .00: Lock 40 | 2- .25: Stats 41 | 3- .50: Release 42 | 4- .75: Move 43 | ''' 44 | 45 | # .00: Lock 46 | if self.base_station is not None: 47 | if self.usage_remaining > 0: 48 | if self.connected: 49 | self.start_consume() 50 | else: 51 | self.connect() 52 | else: 53 | if self.connected: 54 | self.disconnect() 55 | else: 56 | self.generate_usage_and_connect() 57 | 58 | yield self.env.timeout(0.25) 59 | 60 | # .25: Stats 61 | 62 | yield self.env.timeout(0.25) 63 | 64 | # .50: Release 65 | # Base station check skipped as it's already implied by self.connected 66 | if self.connected and self.last_usage > 0: 67 | self.release_consume() 68 | if self.usage_remaining <= 0: 69 | self.disconnect() 70 | 71 | yield self.env.timeout(0.25) 72 | 73 | # .75: Move 74 | # Move the client 75 | x, y = self.mobility_pattern.generate_movement() 76 | self.x += x 77 | self.y += y 78 | 79 | if self.base_station is not None: 80 | if not self.base_station.coverage.is_in_coverage(self.x, self.y): 81 | self.disconnect() 82 | self.assign_closest_base_station(exclude=[self.base_station.pk]) 83 | else: 84 | self.assign_closest_base_station() 85 | 86 | yield self.env.timeout(0.25) 87 | 88 | yield self.env.process(self.iter()) 89 | 90 | def get_slice(self): 91 | if self.base_station is None: 92 | return None 93 | return self.base_station.slices[self.subscribed_slice_index] 94 | 95 | def generate_usage_and_connect(self): 96 | if self.usage_freq < random.random() and self.get_slice() is not None: 97 | # Generate a new usage 98 | self.usage_remaining = self.get_slice().usage_pattern.generate() 99 | self.total_request_count += 1 100 | self.connect() 101 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] requests {self.usage_remaining} usage.') 102 | 103 | def connect(self): 104 | s = self.get_slice() 105 | if self.connected: 106 | return 107 | # increment connect attempt 108 | self.stat_collector.incr_connect_attempt(self) 109 | if s.is_avaliable(): 110 | s.connected_users += 1 111 | self.connected = True 112 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] connected to slice={self.get_slice()} @ {self.base_station}') 113 | return True 114 | else: 115 | self.assign_closest_base_station(exclude=[self.base_station.pk]) 116 | if self.base_station is not None and self.get_slice().is_avaliable(): 117 | # handover 118 | self.stat_collector.incr_handover_count(self) 119 | elif self.base_station is not None: 120 | # block 121 | self.stat_collector.incr_block_count(self) 122 | else: 123 | pass # uncovered 124 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] connection refused to slice={self.get_slice()} @ {self.base_station}') 125 | return False 126 | 127 | def disconnect(self): 128 | if self.connected == False: 129 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] is already disconnected from slice={self.get_slice()} @ {self.base_station}') 130 | else: 131 | slice = self.get_slice() 132 | slice.connected_users -= 1 133 | self.connected = False 134 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] disconnected from slice={self.get_slice()} @ {self.base_station}') 135 | return not self.connected 136 | 137 | def start_consume(self): 138 | s = self.get_slice() 139 | amount = min(s.get_consumable_share(), self.usage_remaining) 140 | # Allocate resource and consume ongoing usage with given bandwidth 141 | s.capacity.get(amount) 142 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] gets {amount} usage.') 143 | self.last_usage = amount 144 | 145 | def release_consume(self): 146 | s = self.get_slice() 147 | # Put the resource back 148 | if self.last_usage > 0: # note: s.capacity.put cannot take 0 149 | s.capacity.put(self.last_usage) 150 | print(f'[{int(self.env.now)}] Client_{self.pk} [{self.x}, {self.y}] puts back {self.last_usage} usage.') 151 | self.total_consume_time += 1 152 | self.total_usage += self.last_usage 153 | self.usage_remaining -= self.last_usage 154 | self.last_usage = 0 155 | 156 | # Check closest base_stations of a client and assign the closest non-excluded avaliable base_station to the client. 157 | def assign_closest_base_station(self, exclude=None): 158 | updated_list = [] 159 | for d,b in self.closest_base_stations: 160 | if exclude is not None and b.pk in exclude: 161 | continue 162 | d = distance((self.x, self.y), (b.coverage.center[0], b.coverage.center[1])) 163 | updated_list.append((d,b)) 164 | updated_list.sort(key=operator.itemgetter(0)) 165 | for d,b in updated_list: 166 | if d <= b.coverage.radius: 167 | self.base_station = b 168 | print(f'[{int(self.env.now)}] Client_{self.pk} freshly assigned to {self.base_station}') 169 | return 170 | if KDTree.last_run_time is not int(self.env.now): 171 | KDTree.run(self.stat_collector.clients, self.stat_collector.base_stations, int(self.env.now), assign=False) 172 | self.base_station = None 173 | 174 | def __str__(self): 175 | return f'Client_{self.pk} [{self.x:<5}, {self.y:>5}] connected to: slice={self.get_slice()} @ {self.base_station}\t with mobility pattern of {self.mobility_pattern}' 176 | -------------------------------------------------------------------------------- /slicesim/Container.py: -------------------------------------------------------------------------------- 1 | class Container: 2 | def __init__(self, init, capacity): 3 | self.capacity = capacity 4 | self.level = init 5 | 6 | def get(self, amount): 7 | if amount <= self.level: 8 | self.level -= amount 9 | return True 10 | else: 11 | return False 12 | 13 | def put(self, amount): 14 | if amount + self.level <= self.capacity: 15 | self.level += amount 16 | return True 17 | else: 18 | return False 19 | -------------------------------------------------------------------------------- /slicesim/Coverage.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | class Coverage: 5 | def __init__(self, center, radius): 6 | self.center = center 7 | self.radius = radius 8 | 9 | def _get_gaussian_distance(self, p): 10 | return math.sqrt(sum((i-j)**2 for i,j in zip(p, self.center))) 11 | 12 | def is_in_coverage(self, x, y): 13 | return self._get_gaussian_distance((x,y)) <= self.radius 14 | 15 | def __str__(self): 16 | x, y = self.center 17 | return f'[c=({x:<4}, {y:>4}), r={self.radius:>4}]' -------------------------------------------------------------------------------- /slicesim/Distributor.py: -------------------------------------------------------------------------------- 1 | class Distributor: 2 | def __init__(self, name, distribution, *dist_params, divide_scale=1): 3 | self.name = name 4 | self.distribution = distribution 5 | self.dist_params = dist_params 6 | self.divide_scale = divide_scale 7 | 8 | def generate(self): 9 | return self.distribution(*self.dist_params) 10 | 11 | def generate_scaled(self): 12 | return self.distribution(*self.dist_params) / self.divide_scale 13 | 14 | def generate_movement(self): 15 | x = self.distribution(*self.dist_params) / self.divide_scale 16 | y = self.distribution(*self.dist_params) / self.divide_scale 17 | return x, y 18 | 19 | def __str__(self): 20 | return f'[{self.name}: {self.distribution.__name__}: {self.dist_params}]' -------------------------------------------------------------------------------- /slicesim/Graph.py: -------------------------------------------------------------------------------- 1 | from statistics import mean 2 | 3 | from matplotlib import gridspec 4 | import matplotlib.animation as animation 5 | import matplotlib.pyplot as plt 6 | from matplotlib.ticker import FormatStrFormatter, FuncFormatter 7 | import randomcolor 8 | 9 | from .utils import format_bps 10 | 11 | 12 | class Graph: 13 | def __init__(self, base_stations, clients, xlim, map_limits, 14 | output_dpi=500, scatter_size=15, output_filename='output.png'): 15 | self.output_filename = output_filename 16 | self.base_stations = base_stations 17 | self.clients = clients 18 | self.xlim = xlim 19 | self.map_limits = map_limits 20 | self.output_dpi = output_dpi 21 | self.scatter_size = scatter_size 22 | self.fig = plt.figure(figsize=(16,9)) 23 | self.fig.canvas.set_window_title('Network Slicing Simulation') 24 | 25 | self.gs = gridspec.GridSpec(4, 3, width_ratios=[6, 3, 3]) 26 | 27 | rand_color = randomcolor.RandomColor() 28 | colors = rand_color.generate(luminosity='bright', count=len(base_stations)) 29 | # colors = [np.random.randint(256*0.2, 256*0.7+1, size=(3,))/256 for __ in range(len(self.base_stations))] 30 | for c, bs in zip(colors, self.base_stations): 31 | bs.color = c 32 | # TODO prevent similar colors 33 | 34 | def draw_live(self, *stats): 35 | ani = animation.FuncAnimation(self.fig, self.draw_all, fargs=stats, interval=1000) 36 | plt.show() 37 | 38 | def draw_all(self, *stats): 39 | plt.clf() 40 | self.draw_map() 41 | self.draw_stats(*stats) 42 | 43 | def draw_map(self): 44 | markers = ['o', 's', 'p', 'P', '*', 'H', 'X', 'D', 'v', '^', '<', '>', '1', '2', '3', '4'] 45 | self.ax = plt.subplot(self.gs[:, 0]) 46 | xlims, ylims = self.map_limits 47 | self.ax.set_xlim(xlims) 48 | self.ax.set_ylim(ylims) 49 | self.ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f m')) 50 | self.ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f m')) 51 | self.ax.set_aspect('equal') 52 | 53 | # base stations 54 | for bs in self.base_stations: 55 | circle = plt.Circle(bs.coverage.center, bs.coverage.radius, 56 | fill=False, linewidth=2, alpha=0.9, color=bs.color) 57 | self.ax.add_artist(circle) 58 | 59 | # clients 60 | legend_indexed = [] 61 | for c in self.clients: 62 | label = None 63 | if c.subscribed_slice_index not in legend_indexed and c.base_station is not None: 64 | label = c.get_slice().name 65 | legend_indexed.append(c.subscribed_slice_index) 66 | self.ax.scatter(c.x, c.y, 67 | color=c.base_station.color if c.base_station is not None else '0.8', 68 | label=label, s=15, 69 | marker=markers[c.subscribed_slice_index % len(markers)]) 70 | 71 | box = self.ax.get_position() 72 | self.ax.set_position([box.x0 - box.width * 0.05, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) 73 | 74 | leg = self.ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.05), 75 | shadow=True, ncol=5) 76 | 77 | for i in range(len(legend_indexed)): 78 | leg.legendHandles[i].set_color('k') 79 | 80 | def draw_stats(self, vals, vals1, vals2, vals3, vals4, vals5, vals6): 81 | self.ax1 = plt.subplot(self.gs[0, 1]) 82 | self.ax1.plot(vals) 83 | self.ax1.set_xlim(self.xlim) 84 | locs = self.ax1.get_xticks() 85 | locs[0] = self.xlim[0] 86 | locs[-1] = self.xlim[1] 87 | self.ax1.set_xticks(locs) 88 | self.ax1.use_sticky_edges = False 89 | self.ax1.set_title(f'Connected Clients Ratio') 90 | 91 | self.ax2 = plt.subplot(self.gs[1, 1]) 92 | self.ax2.plot(vals1) 93 | self.ax2.set_xlim(self.xlim) 94 | self.ax2.set_xticks(locs) 95 | self.ax2.yaxis.set_major_formatter(FuncFormatter(format_bps)) 96 | self.ax2.use_sticky_edges = False 97 | self.ax2.set_title('Total Bandwidth Usage') 98 | 99 | self.ax3 = plt.subplot(self.gs[2, 1]) 100 | self.ax3.plot(vals2) 101 | self.ax3.set_xlim(self.xlim) 102 | self.ax3.set_xticks(locs) 103 | self.ax3.use_sticky_edges = False 104 | self.ax3.set_title('Bandwidth Usage Ratio in Slices (Averaged)') 105 | 106 | self.ax4 = plt.subplot(self.gs[3, 1]) 107 | self.ax4.plot(vals3) 108 | self.ax4.set_xlim(self.xlim) 109 | self.ax4.set_xticks(locs) 110 | self.ax4.use_sticky_edges = False 111 | self.ax4.set_title('Client Count Ratio per Slice') 112 | 113 | self.ax5 = plt.subplot(self.gs[0, 2]) 114 | self.ax5.plot(vals4) 115 | self.ax5.set_xlim(self.xlim) 116 | self.ax5.set_xticks(locs) 117 | self.ax5.use_sticky_edges = False 118 | self.ax5.set_title('Coverage Ratio') 119 | 120 | self.ax6 = plt.subplot(self.gs[1, 2]) 121 | self.ax6.plot(vals5) 122 | self.ax6.set_xlim(self.xlim) 123 | self.ax6.set_xticks(locs) 124 | self.ax6.yaxis.set_major_formatter(FormatStrFormatter('%.3f')) 125 | self.ax6.use_sticky_edges = False 126 | self.ax6.set_title('Block ratio') 127 | 128 | self.ax7 = plt.subplot(self.gs[2, 2]) 129 | self.ax7.plot(vals6) 130 | self.ax7.set_xlim(self.xlim) 131 | self.ax7.set_xticks(locs) 132 | self.ax7.yaxis.set_major_formatter(FormatStrFormatter('%.3f')) 133 | self.ax7.use_sticky_edges = False 134 | self.ax7.set_title('Handover ratio') 135 | 136 | self.ax8 = plt.subplot(self.gs[3, 2]) 137 | row_labels = [ 138 | 'Initial number of clients', 139 | 'Average connected clients', 140 | 'Average bandwidth usage', 141 | 'Average load factor of slices', 142 | 'Average coverage ratio', 143 | 'Average block ratio', 144 | 'Average handover ratio', 145 | ] 146 | l, r = self.xlim 147 | cell_text = [ 148 | [f'{len(self.clients)}'], 149 | [f'{mean(vals[l:r]):.2f}'], 150 | [f'{format_bps(mean(vals1[l:r]), return_float=True)}'], 151 | [f'{mean(vals2[l:r]):.2f}'], 152 | [f'{mean(vals4[l:r]):.2f}'], 153 | [f'{mean(vals5[l:r]):.4f}'], 154 | [f'{mean(vals6[l:r]):.4f}'], 155 | ] 156 | 157 | self.ax8.axis('off') 158 | self.ax8.axis('tight') 159 | self.ax8.tick_params(axis='x', which='major', pad=15) 160 | self.ax8.table(cellText=cell_text, rowLabels=row_labels, colWidths=[0.35, 0.2], loc='center right') 161 | 162 | plt.tight_layout() 163 | 164 | def save_fig(self): 165 | self.fig.savefig(self.output_filename, dpi=1000) 166 | 167 | def show_plot(self): 168 | plt.show() 169 | 170 | def get_map_limits(self): 171 | # deprecated 172 | x_min = min([bs.coverage.center[0]-bs.coverage.radius for bs in self.base_stations]) 173 | x_max = max([bs.coverage.center[0]+bs.coverage.radius for bs in self.base_stations]) 174 | y_min = min([bs.coverage.center[1]-bs.coverage.radius for bs in self.base_stations]) 175 | y_max = max([bs.coverage.center[1]+bs.coverage.radius for bs in self.base_stations]) 176 | 177 | return (x_min, x_max), (y_min, y_max) 178 | -------------------------------------------------------------------------------- /slicesim/Slice.py: -------------------------------------------------------------------------------- 1 | class Slice: 2 | def __init__(self, name, ratio, 3 | connected_users, user_share, delay_tolerance, qos_class, 4 | bandwidth_guaranteed, bandwidth_max, init_capacity, 5 | usage_pattern): 6 | self.name = name 7 | self.connected_users = connected_users 8 | self.user_share = user_share 9 | self.delay_tolerance = delay_tolerance 10 | self.qos_class = qos_class 11 | self.ratio = ratio 12 | self.bandwidth_guaranteed = bandwidth_guaranteed 13 | self.bandwidth_max = bandwidth_max 14 | self.init_capacity = init_capacity 15 | self.capacity = 0 16 | self.usage_pattern = usage_pattern 17 | 18 | def get_consumable_share(self): 19 | if self.connected_users <= 0: 20 | return min(self.init_capacity, self.bandwidth_max) 21 | else: 22 | return min(self.init_capacity/self.connected_users, self.bandwidth_max) 23 | 24 | def is_avaliable(self): 25 | real_cap = min(self.init_capacity, self.bandwidth_max) 26 | bandwidth_next = real_cap / (self.connected_users + 1) 27 | if bandwidth_next < self.bandwidth_guaranteed: 28 | return False 29 | return True 30 | 31 | def __str__(self): 32 | return f'{self.name:<10} init={self.init_capacity:<5} cap={self.capacity.level:<5} diff={(self.init_capacity - self.capacity.level):<5}' 33 | -------------------------------------------------------------------------------- /slicesim/Stats.py: -------------------------------------------------------------------------------- 1 | class Stats: 2 | def __init__(self, env, base_stations, clients, area): 3 | self.env = env 4 | self.base_stations = base_stations 5 | self.clients = clients 6 | self.area = area 7 | #self.graph = graph 8 | 9 | # Stats 10 | self.total_connected_users_ratio = [] 11 | self.total_used_bw = [] 12 | self.avg_slice_load_ratio = [] 13 | self.avg_slice_client_count = [] 14 | self.coverage_ratio = [] 15 | self.connect_attempt = [] 16 | self.block_count = [] 17 | self.handover_count = [] 18 | 19 | def get_stats(self): 20 | return ( 21 | self.total_connected_users_ratio, 22 | self.total_used_bw, 23 | self.avg_slice_load_ratio, 24 | self.avg_slice_client_count, 25 | self.coverage_ratio, 26 | self.block_count, 27 | self.handover_count, 28 | ) 29 | 30 | def collect(self): 31 | yield self.env.timeout(0.25) 32 | self.connect_attempt.append(0) 33 | self.block_count.append(0) 34 | self.handover_count.append(0) 35 | while True: 36 | self.block_count[-1] /= self.connect_attempt[-1] if self.connect_attempt[-1] != 0 else 1 37 | self.handover_count[-1] /= self.connect_attempt[-1] if self.connect_attempt[-1] != 0 else 1 38 | 39 | self.total_connected_users_ratio.append(self.get_total_connected_users_ratio()) 40 | self.total_used_bw.append(self.get_total_used_bw()) 41 | self.avg_slice_load_ratio.append(self.get_avg_slice_load_ratio()) 42 | self.avg_slice_client_count.append(self.get_avg_slice_client_count()) 43 | self.coverage_ratio.append(self.get_coverage_ratio()) 44 | 45 | self.connect_attempt.append(0) 46 | self.block_count.append(0) 47 | self.handover_count.append(0) 48 | yield self.env.timeout(1) 49 | 50 | def get_total_connected_users_ratio(self): 51 | t, cc = 0, 0 52 | for c in self.clients: 53 | if self.is_client_in_coverage(c): 54 | t += c.connected 55 | cc += 1 56 | # for bs in self.base_stations: 57 | # for sl in bs.slices: 58 | # t += sl.connected_users 59 | return t/cc if cc != 0 else 0 60 | 61 | def get_total_used_bw(self): 62 | t = 0 63 | for bs in self.base_stations: 64 | for sl in bs.slices: 65 | t += sl.capacity.capacity - sl.capacity.level 66 | return t 67 | 68 | def get_avg_slice_load_ratio(self): 69 | t, c = 0, 0 70 | for bs in self.base_stations: 71 | for sl in bs.slices: 72 | c += sl.capacity.capacity 73 | t += sl.capacity.capacity - sl.capacity.level 74 | #c += 1 75 | #t += (sl.capacity.capacity - sl.capacity.level) / sl.capacity.capacity 76 | return t/c if c !=0 else 0 77 | 78 | def get_avg_slice_client_count(self): 79 | t, c = 0, 0 80 | for bs in self.base_stations: 81 | for sl in bs.slices: 82 | c += 1 83 | t += sl.connected_users 84 | return t/c if c !=0 else 0 85 | 86 | def get_coverage_ratio(self): 87 | t, cc = 0, 0 88 | for c in self.clients: 89 | if self.is_client_in_coverage(c): 90 | cc += 1 91 | if c.base_station is not None and c.base_station.coverage.is_in_coverage(c.x, c.y): 92 | t += 1 93 | return t/cc if cc !=0 else 0 94 | 95 | def incr_connect_attempt(self, client): 96 | if self.is_client_in_coverage(client): 97 | self.connect_attempt[-1] += 1 98 | 99 | def incr_block_count(self, client): 100 | if self.is_client_in_coverage(client): 101 | self.block_count[-1] += 1 102 | 103 | def incr_handover_count(self, client): 104 | if self.is_client_in_coverage(client): 105 | self.handover_count[-1] += 1 106 | 107 | def is_client_in_coverage(self, client): 108 | xs, ys = self.area 109 | return True if xs[0] <= client.x <= xs[1] and ys[0] <= client.y <= ys[1] else False 110 | -------------------------------------------------------------------------------- /slicesim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerob/slicesim/af3ac84ed7f5f4430c521d70c362caf21aa720f4/slicesim/__init__.py -------------------------------------------------------------------------------- /slicesim/__main__.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import random 4 | import sys 5 | 6 | import simpy 7 | import yaml 8 | 9 | from .BaseStation import BaseStation 10 | from .Client import Client 11 | from .Coverage import Coverage 12 | from .Distributor import Distributor 13 | from .Graph import Graph 14 | from .Slice import Slice 15 | from .Stats import Stats 16 | 17 | from .utils import KDTree 18 | 19 | 20 | def get_dist(d): 21 | return { 22 | 'randrange': random.randrange, # start, stop, step 23 | 'randint': random.randint, # a, b 24 | 'random': random.random, 25 | 'uniform': random, # a, b 26 | 'triangular': random.triangular, # low, high, mode 27 | 'beta': random.betavariate, # alpha, beta 28 | 'expo': random.expovariate, # lambda 29 | 'gamma': random.gammavariate, # alpha, beta 30 | 'gauss': random.gauss, # mu, sigma 31 | 'lognorm': random.lognormvariate, # mu, sigma 32 | 'normal': random.normalvariate, # mu, sigma 33 | 'vonmises': random.vonmisesvariate, # mu, kappa 34 | 'pareto': random.paretovariate, # alpha 35 | 'weibull': random.weibullvariate # alpha, beta 36 | }.get(d) 37 | 38 | 39 | def get_random_mobility_pattern(vals, mobility_patterns): 40 | i = 0 41 | r = random.random() 42 | 43 | while vals[i] < r: 44 | i += 1 45 | 46 | return mobility_patterns[i] 47 | 48 | 49 | def get_random_slice_index(vals): 50 | i = 0 51 | r = random.random() 52 | 53 | while vals[i] < r: 54 | i += 1 55 | return i 56 | 57 | if len(sys.argv) != 2: 58 | print('Please type an input file.') 59 | print('python -m slicesim ') 60 | exit(1) 61 | 62 | # Read YAML file 63 | CONF_FILENAME = os.path.join(os.path.dirname(__file__), sys.argv[1]) 64 | try: 65 | with open(CONF_FILENAME, 'r') as stream: 66 | data = yaml.load(stream, Loader=yaml.FullLoader) 67 | except FileNotFoundError: 68 | print('File Not Found:', CONF_FILENAME) 69 | exit(0) 70 | 71 | random.seed() 72 | env = simpy.Environment() 73 | 74 | SETTINGS = data['settings'] 75 | SLICES_INFO = data['slices'] 76 | NUM_CLIENTS = SETTINGS['num_clients'] 77 | MOBILITY_PATTERNS = data['mobility_patterns'] 78 | BASE_STATIONS = data['base_stations'] 79 | CLIENTS = data['clients'] 80 | 81 | if SETTINGS['logging']: 82 | sys.stdout = open(SETTINGS['log_file'],'wt') 83 | else: 84 | sys.stdout = open(os.devnull, 'w') 85 | 86 | collected, slice_weights = 0, [] 87 | for __, s in SLICES_INFO.items(): 88 | collected += s['client_weight'] 89 | slice_weights.append(collected) 90 | 91 | collected, mb_weights = 0, [] 92 | for __, mb in MOBILITY_PATTERNS.items(): 93 | collected += mb['client_weight'] 94 | mb_weights.append(collected) 95 | 96 | mobility_patterns = [] 97 | for name, mb in MOBILITY_PATTERNS.items(): 98 | mobility_pattern = Distributor(name, get_dist(mb['distribution']), *mb['params']) 99 | mobility_patterns.append(mobility_pattern) 100 | 101 | usage_patterns = {} 102 | for name, s in SLICES_INFO.items(): 103 | usage_patterns[name] = Distributor(name, get_dist(s['usage_pattern']['distribution']), *s['usage_pattern']['params']) 104 | 105 | base_stations = [] 106 | i = 0 107 | for b in BASE_STATIONS: 108 | slices = [] 109 | ratios = b['ratios'] 110 | capacity = b['capacity_bandwidth'] 111 | for name, s in SLICES_INFO.items(): 112 | s_cap = capacity * ratios[name] 113 | # TODO remove bandwidth max 114 | s = Slice(name, ratios[name], 0, s['client_weight'], 115 | s['delay_tolerance'], 116 | s['qos_class'], s['bandwidth_guaranteed'], 117 | s['bandwidth_max'], s_cap, usage_patterns[name]) 118 | s.capacity = simpy.Container(env, init=s_cap, capacity=s_cap) 119 | slices.append(s) 120 | base_station = BaseStation(i, Coverage((b['x'], b['y']), b['coverage']), capacity, slices) 121 | base_stations.append(base_station) 122 | i += 1 123 | 124 | ufp = CLIENTS['usage_frequency'] 125 | usage_freq_pattern = Distributor(f'ufp', get_dist(ufp['distribution']), *ufp['params'], divide_scale=ufp['divide_scale']) 126 | 127 | x_vals = SETTINGS['statistics_params']['x'] 128 | y_vals = SETTINGS['statistics_params']['y'] 129 | stats = Stats(env, base_stations, None, ((x_vals['min'], x_vals['max']), (y_vals['min'], y_vals['max']))) 130 | 131 | clients = [] 132 | for i in range(NUM_CLIENTS): 133 | loc_x = CLIENTS['location']['x'] 134 | loc_y = CLIENTS['location']['y'] 135 | location_x = get_dist(loc_x['distribution'])(*loc_x['params']) 136 | location_y = get_dist(loc_y['distribution'])(*loc_y['params']) 137 | 138 | mobility_pattern = get_random_mobility_pattern(mb_weights, mobility_patterns) 139 | 140 | connected_slice_index = get_random_slice_index(slice_weights) 141 | c = Client(i, env, location_x, location_y, 142 | mobility_pattern, usage_freq_pattern.generate_scaled(), connected_slice_index, stats) 143 | clients.append(c) 144 | 145 | KDTree.limit = SETTINGS['limit_closest_base_stations'] 146 | KDTree.run(clients, base_stations, 0) 147 | 148 | stats.clients = clients 149 | env.process(stats.collect()) 150 | 151 | env.run(until=int(SETTINGS['simulation_time'])) 152 | 153 | for client in clients: 154 | print(client) 155 | print(f'\tTotal connected time: {client.total_connected_time:>5}') 156 | print(f'\tTotal unconnected time: {client.total_unconnected_time:>5}') 157 | print(f'\tTotal request count: {client.total_request_count:>5}') 158 | print(f'\tTotal consume time: {client.total_consume_time:>5}') 159 | print(f'\tTotal usage: {client.total_usage:>5}') 160 | print() 161 | 162 | print(stats.get_stats()) 163 | 164 | if SETTINGS['plotting_params']['plotting']: 165 | xlim_left = int(SETTINGS['simulation_time'] * SETTINGS['statistics_params']['warmup_ratio']) 166 | xlim_right = int(SETTINGS['simulation_time'] * (1 - SETTINGS['statistics_params']['cooldown_ratio'])) + 1 167 | 168 | graph = Graph(base_stations, clients, (xlim_left, xlim_right), 169 | ((x_vals['min'], x_vals['max']), (y_vals['min'], y_vals['max'])), 170 | output_dpi=SETTINGS['plotting_params']['plot_file_dpi'], 171 | scatter_size=SETTINGS['plotting_params']['scatter_size'], 172 | output_filename=SETTINGS['plotting_params']['plot_file']) 173 | graph.draw_all(*stats.get_stats()) 174 | if SETTINGS['plotting_params']['plot_save']: 175 | graph.save_fig() 176 | if SETTINGS['plotting_params']['plot_show']: 177 | graph.show_plot() 178 | 179 | sys.stdout = sys.__stdout__ 180 | print('Simulation has ran completely and output file created to:', SETTINGS['log_file']) 181 | -------------------------------------------------------------------------------- /slicesim/example-input.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | simulation_time: 100 3 | num_clients: 1000 4 | limit_closest_base_stations: 7 5 | statistics_params: 6 | warmup_ratio: 0.05 7 | cooldown_ratio: 0.05 8 | logging: False 9 | log_file: output.txt 10 | plotting: True 11 | plot_save: True 12 | plot_show: True 13 | plot_file: output.png 14 | slices: 15 | iot: 16 | delay_tolerance: 10 17 | qos_class: 2 18 | bandwidth_guaranteed: 10 19 | bandwidth_max: 10000000 20 | client_weight: 0.5 21 | threshold: 0.8 22 | data: 23 | delay_tolerance: 2000 24 | qos_class: 4 25 | bandwidth_guaranteed: 1000 26 | bandwidth_max: 50000000 27 | client_weight: 0.5 28 | threshold: 0.87 29 | base_stations: 30 | - x: 0 31 | y: 0 32 | coverage: 600 33 | capacity_bandwidth: 1000000 34 | ratios: 35 | iot: 0.4 36 | data: 0.6 37 | - x: 1000 38 | y: 500 39 | coverage: 700 40 | capacity_bandwidth: 1400000 41 | ratios: 42 | iot: 0.2 43 | data: 0.8 44 | - x: -500 45 | y: -500 46 | coverage: 500 47 | capacity_bandwidth: 700000 48 | ratios: 49 | iot: 0.24 50 | data: 0.76 51 | - x: -750 52 | y: 850 53 | coverage: 800 54 | capacity_bandwidth: 1200000 55 | ratios: 56 | iot: 0.10 57 | data: 0.90 58 | - x: 450 59 | y: -950 60 | coverage: 900 61 | capacity_bandwidth: 1000000 62 | ratios: 63 | iot: 0.20 64 | data: 0.80 65 | - x: -1300 66 | y: -1300 67 | coverage: 400 68 | capacity_bandwidth: 120000 69 | ratios: 70 | iot: 0.01 71 | data: 0.99 72 | - x: 1100 73 | y: 1300 74 | coverage: 357 75 | capacity_bandwidth: 100000 76 | ratios: 77 | iot: 0.50 78 | data: 0.50 79 | mobility_patterns: 80 | normal_btw_0_1000: 81 | distribution: normal 82 | params: 83 | - 0 84 | - 1 85 | client_weight: 0.5 86 | basic: 87 | distribution: randint 88 | params: 89 | - -1 90 | - 1 91 | client_weight: 0.5 92 | clients: 93 | location: 94 | x: 95 | distribution: normal 96 | params: 97 | - 0 98 | - 600 99 | y: 100 | distribution: normal 101 | params: 102 | - 0 103 | - 600 104 | usage: 105 | distribution: randint 106 | params: 107 | - 0 108 | - 100000 109 | usage_frequency: 110 | distribution: randint 111 | params: 112 | - 0 113 | - 100000 114 | divide_scale: 1000000 -------------------------------------------------------------------------------- /slicesim/utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from sklearn.neighbors import KDTree as kdt 4 | 5 | def distance(a, b): 6 | return math.sqrt(sum((i-j)**2 for i,j in zip(a, b))) 7 | 8 | # Initial connections using k-d tree 9 | def kdtree(clients, base_stations): 10 | 11 | c_coor = [(c.x,c.y) for c in clients] 12 | bs_coor = [p.coverage.center for p in base_stations] 13 | 14 | tree = KDTree(bs_coor, leaf_size=2) 15 | res = tree.query(c_coor) 16 | 17 | for c, d, p in zip(clients, res[0], res[1]): 18 | if d[0] <= base_stations[p[0]].coverage.radius: 19 | c.base_station = base_stations[p[0]] 20 | 21 | class KDTree: 22 | last_run_time = 0 23 | limit = None 24 | 25 | # Initial connections using k-d tree 26 | @staticmethod 27 | def run(clients, base_stations, run_at, assign=True): 28 | print(f'KDTREE CALL [{run_at}] - limit: {KDTree.limit}') 29 | if run_at == KDTree.last_run_time: 30 | return 31 | KDTree.last_run_time = run_at 32 | 33 | c_coor = [(c.x,c.y) for c in clients] 34 | bs_coor = [p.coverage.center for p in base_stations] 35 | 36 | tree = kdt(bs_coor, leaf_size=2) 37 | res = tree.query(c_coor,k=min(KDTree.limit,len(base_stations))) 38 | 39 | # print(res[0]) 40 | for c, d, p in zip(clients, res[0], res[1]): 41 | if assign and d[0] <= base_stations[p[0]].coverage.radius: 42 | c.base_station = base_stations[p[0]] 43 | c.closest_base_stations = [(a, base_stations[b]) for a,b in zip(d,p)] 44 | 45 | 46 | def format_bps(size, pos=None, return_float=False): 47 | # https://stackoverflow.com/questions/12523586/python-format-size-application-converting-b-to-kb-mb-gb-tb 48 | power, n = 1000, 0 49 | power_labels = {0 : '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'} 50 | while size >= power: 51 | size /= power 52 | n += 1 53 | if return_float: 54 | return f'{size:.3f} {power_labels[n]}bps' 55 | return f'{size:.0f} {power_labels[n]}bps' 56 | --------------------------------------------------------------------------------