├── .github └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .readthedocs.yml ├── LICENSE ├── README.md ├── docs ├── _static │ ├── custom.css │ ├── des.png │ ├── graph-model.png │ ├── infrastructure.png │ └── logo.svg ├── changelog.rst ├── conf.py ├── index.rst └── reference │ ├── application.rst │ ├── infrastructure.rst │ ├── modules.rst │ ├── orchestrator.rst │ └── power.rst ├── examples ├── 1_single_node.py ├── 2_application_placement.py └── smart_city_traffic │ ├── analysis │ ├── create_plots.py │ ├── environment.yml │ ├── figures.py │ └── settings.py │ ├── city.py │ ├── infrastructure.py │ ├── main.py │ ├── mobility.py │ ├── orchestrator.py │ ├── results │ ├── fog_0 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ ├── fog_1 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ ├── fog_2 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ ├── fog_3 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ ├── fog_4 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ ├── fog_5 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ ├── fog_6 │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ └── fog_6_shutdown │ │ ├── applications.pdf │ │ └── infrastructure.pdf │ └── settings.py ├── leaf ├── application.py ├── infrastructure.py ├── mobility.py ├── orchestrator.py └── power.py └── setup.py /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.7, 3.11] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | python -m pip install . 26 | - name: Run example 1 27 | run: python examples/1_single_node.py 28 | - name: Run example 2 29 | run: python examples/2_application_placement.py 30 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish new release on PyPI 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Fetch all history for all tags and branches 13 | run: git fetch --prune --unshallow 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.6' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | python -m pip install . 22 | - run: python setup.py --version 23 | - name: Build 24 | run: python setup.py sdist 25 | - name: Publish 26 | uses: pypa/gh-action-pypi-publish@release/v1 27 | with: 28 | user: __token__ 29 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project 2 | **/results/**/*.csv 3 | 4 | # IDE 5 | .idea 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | cover/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | .pybuilder/ 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | # For a library or package, you might want to ignore these files since the code is 93 | # intended to run in multiple environments; otherwise, check them in: 94 | # .python-version 95 | 96 | # pipenv 97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 100 | # install all needed dependencies. 101 | #Pipfile.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | 122 | # Spyder project settings 123 | .spyderproject 124 | .spyproject 125 | 126 | # Rope project settings 127 | .ropeproject 128 | 129 | # mkdocs documentation 130 | /site 131 | 132 | # mypy 133 | .mypy_cache/ 134 | .dmypy.json 135 | dmypy.json 136 | 137 | # Pyre type checker 138 | .pyre/ 139 | 140 | # pytype static type analyzer 141 | .pytype/ 142 | 143 | # Cython debug symbols 144 | cython_debug/ 145 | 146 | # Other 147 | .DS_Store 148 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | sphinx: 4 | configuration: docs/conf.py 5 | 6 | python: 7 | version: 3.8 # Important for being able to import importlib.metadata 8 | install: 9 | - method: pip 10 | path: . 11 | extra_requirements: 12 | - docs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2020 Python Packaging Authority 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LEAF [![PyPI version](https://img.shields.io/pypi/v/leafsim.svg?color=52c72b)](https://pypi.org/project/leafsim/) [![Supported versions](https://img.shields.io/pypi/pyversions/leafsim.svg)](https://pypi.org/project/leafsim/) [![License](https://img.shields.io/pypi/l/leafsim.svg)](https://pypi.org/project/leafsim/) 2 | 3 | 4 | 5 | LEAF is a simulator for analytical modeling of energy consumption in cloud, fog, or edge computing environments. 6 | It enables the modeling of simple tasks running on a single node as well as complex application graphs in distributed, heterogeneous, and resource-constrained infrastructures. 7 | LEAF is based on [SimPy](https://simpy.readthedocs.io/en/latest/) for discrete-event simulation and [NetworkX](https://networkx.org/) for modeling infrastructure or application graphs. 8 | 9 | Please have a look at out [examples](https://github.com/dos-group/leaf/tree/main/examples) and visit the official [documentation](https://leaf.readthedocs.io) for more information on this project. 10 | 11 | This Python implementation was ported from the [original Java protoype](https://www.github.com/birnbaum/leaf). 12 | All future development will take place in this repository. 13 | 14 | 15 | ## ⚙️ Installation 16 | 17 | You can install the [latest release](https://pypi.org/project/leafsim/) of LEAF via [pip](https://pip.pypa.io/en/stable/quickstart/): 18 | 19 | ``` 20 | $ pip install leafsim 21 | ``` 22 | 23 | Alternatively, you can also clone the repository (including all examples) and set up your environment via: 24 | 25 | ``` 26 | $ pip install -e . 27 | ``` 28 | 29 | 30 | ## 🚀 Getting started 31 | 32 | LEAF uses [SimPy](https://simpy.readthedocs.io/en/latest/) for process-based discrete-event simulation and adheres to their API. 33 | To understand how to develop scenarios in LEAF, it makes sense to familiarize yourself with SimPy first. 34 | 35 | ```python 36 | import simpy 37 | from leaf.application import Task 38 | from leaf.infrastructure import Node 39 | from leaf.power import PowerModelNode, PowerMeter 40 | 41 | # Processes modify the model during the simulation 42 | def place_task_after_2_seconds(env, node, task): 43 | """Waits for 2 seconds and places a task on a node.""" 44 | yield env.timeout(2) 45 | task.allocate(node) 46 | 47 | node = Node("node1", cu=100, power_model=PowerModelNode(max_power=30, static_power=10)) 48 | task = Task(cu=100) 49 | power_meter = PowerMeter(node, callback=lambda m: print(f"{env.now}: Node consumes {int(m)}W")) 50 | 51 | env = simpy.Environment() 52 | # register our task placement process 53 | env.process(place_task_after_2_seconds(env, node, task)) 54 | # register power metering process (provided by LEAF) 55 | env.process(power_meter.run(env)) 56 | env.run(until=5) 57 | ``` 58 | 59 | Which will result in the output: 60 | 61 | ``` 62 | 0: Node consumes 10W 63 | 1: Node consumes 10W 64 | 2: Node consumes 30W 65 | 3: Node consumes 30W 66 | 4: Node consumes 30W 67 | ``` 68 | 69 | For other examples, please refer to the [examples folder](https://github.com/dos-group/leaf/blob/main/examples). 70 | 71 | 72 | ## 🍃 What can I do with LEAF? 73 | 74 | LEAF enables high-level simulation of computing scenarios, where experiments are easy to create and easy to analyze. 75 | Besides allowing research on scheduling and placement algorithms on resource-constrained environments, LEAF puts a special focus on: 76 | 77 | - **Dynamic networks**: Simulate mobile nodes which can join or leave the network during the simulation. 78 | - **Power consumption modeling**: Model the power usage of individual compute nodes, network traffic, and applications. 79 | - **Energy-aware algorithms**: Implement dynamically adapting task placement strategies, routing policies, and other energy-saving mechanisms. 80 | - **Scalability**: Model the execution of thousands of compute nodes and applications in magnitudes faster than real time. 81 | 82 | Please visit the official [documentation](https://leaf.readthedocs.io) for more information and examples on this project. 83 | 84 |

85 | 86 |

87 | 88 | 89 | ## 📖 Publications 90 | 91 | If you use LEAF in your research, please cite our paper: 92 | 93 | Philipp Wiesner and Lauritz Thamsen. "[LEAF: Simulating Large Energy-Aware Fog Computing Environments](https://ieeexplore.ieee.org/document/9458907)" In the Proceedings of the 2021 *5th IEEE International Conference on Fog and Edge Computing (ICFEC)*. IEEE. 2021 [[arXiv preprint]](https://arxiv.org/pdf/2103.01170.pdf) [[video]](https://youtu.be/G70hudAhd5M) 94 | 95 | Bibtex: 96 | ``` 97 | @inproceedings{WiesnerThamsen_LEAF_2021, 98 | author={Wiesner, Philipp and Thamsen, Lauritz}, 99 | booktitle={2021 IEEE 5th International Conference on Fog and Edge Computing (ICFEC)}, 100 | title={{LEAF}: Simulating Large Energy-Aware Fog Computing Environments}, 101 | year={2021}, 102 | pages={29-36}, 103 | doi={10.1109/ICFEC51620.2021.00012} 104 | } 105 | ``` 106 | 107 | ## 💚 Projects using LEAF 108 | 109 | - Shivani Tripathi, Praveen Kumar, Priyadarshni Gupta, Rajiv Misra, and T.N. Singh. "[Workload Shifting Based on Low Carbon Intensity Periods: A Framework for Reducing Carbon Emissions in Cloud Computing]()". *2023 IEEE International Conference on Big Data (BigData)*. IEEE. 2023. 110 | - Rui Zhang, Xuesen Chu, Ruhui Ma, Meng Zhang, Liwei Lin, Honghao Gao, and Haibing Guan. "[OSTTD: Offloading of Splittable Tasks with Topological Dependence in Multi-Tier Computing Networks](https://ieeexplore.ieee.org/abstract/document/9978666)". *IEEE Journal on Selected Areas in Communications*. 2022. 111 | - Zizheng Liu. "A Web-Based User Interface for the LEAF Simulator". *MSc IT+ Dissertation* at U of Glasgow. 2022 [[code](https://github.com/ZZZZZZZZZED/leaf-GUI)] 112 | - Yana Kernerman. "Interactive Visualization of Energy Consumption in Edge and Fog Computing Simulations". *Bachelor Thesis* at TU Berlin. 2022 [[code](https://github.com/dos-group/leaf/tree/gui)] 113 | - Sangeeta Kakati and Rupa Deka. "[Computational and Adaptive Offloading in Edge/Fog based IoT Environments](https://ieeexplore.ieee.org/document/9847743)." *2nd International Conference on Intelligent Technologies (CONIT)*. IEEE. 2022 114 | - Philipp Wiesner, Ilja Behnke, Dominik Scheinert, Kordian Gontarska, and Lauritz Thamsen. "[Let's Wait Awhile: How Temporal Workload Shifting Can Reduce Carbon Emissions in the Cloud](https://arxiv.org/pdf/2110.13234.pdf)". *22nd International Middleware Conference*. ACM. 2021 [[code](https://github.com/dos-group/lets-wait-awhile)] 115 | - Liam Brugger. "An Evaluation of Carbon-Aware Load Shifting Techniques". *Bachelor Thesis* at TU Berlin. 2021 [[code](https://gitlab.com/lbrugger72/Bachelor)] 116 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | dl.py.class { 2 | margin-bottom: 50px; 3 | } -------------------------------------------------------------------------------- /docs/_static/des.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/docs/_static/des.png -------------------------------------------------------------------------------- /docs/_static/graph-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/docs/_static/graph-model.png -------------------------------------------------------------------------------- /docs/_static/infrastructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/docs/_static/infrastructure.png -------------------------------------------------------------------------------- /docs/_static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.1.2 (2021-03-10) 5 | ------------------ 6 | 7 | Few small bugfixes 8 | 9 | 10 | 0.1.1 (2021-02-03) 11 | ------------------ 12 | 13 | Initial Release -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from datetime import datetime 5 | import alabaster 6 | 7 | sys.path.insert(0, os.path.abspath('../leaf')) 8 | 9 | 10 | # -- Project information ----------------------------------------------------- 11 | 12 | project = "LEAF" 13 | author = "Philipp Wiesner" 14 | copyright = f"{datetime.now().year} {author}" 15 | # The short X.Y version 16 | 17 | try: 18 | import importlib.metadata 19 | version = importlib.metadata.version('leafsim') 20 | except ModuleNotFoundError: # Python <3.8 21 | import pkg_resources 22 | version = pkg_resources.get_distribution('leaf').version 23 | # The full version, including alpha/beta/rc tags 24 | release = version 25 | 26 | html_theme_path = [alabaster.get_path()] 27 | 28 | extensions = [ 29 | "sphinx.ext.autodoc", 30 | "sphinx.ext.napoleon", 31 | "alabaster", 32 | ] 33 | html_static_path = ["_static"] 34 | html_theme_options = { 35 | "logo": "logo.svg", 36 | "logo_name": True, 37 | "logo_text_align": "center", 38 | "description": "Simulator for modeling energy consumption in cloud, fog and edge computing environments.", 39 | "github_user": "dos-group", 40 | "github_repo": "leaf", 41 | "github_banner": True, 42 | "github_button": True, 43 | #"travis_button": True, 44 | #"codecov_button": True, 45 | #"tidelift_url": "https://tidelift.com/subscription/pkg/pypi-fabric?utm_source=pypi-fabric&utm_medium=referral&utm_campaign=docs", 46 | #"analytics_id": "UA-18486793-1", 47 | "link": "#3782BE", 48 | "link_hover": "#3782BE", 49 | # Wide enough that 80-col code snippets aren't truncated on default font 50 | # settings (at least for bitprophet's Chrome-on-OSX-Yosemite setup) 51 | "page_width": "1024px", 52 | } 53 | 54 | html_theme = 'alabaster' 55 | 56 | # Both the class’ and the __init__ method’s docstring are concatenated and inserted. 57 | autoclass_content = "both" 58 | 59 | # Add any paths that contain templates here, relative to this directory. 60 | templates_path = ['_templates'] 61 | 62 | # The suffix(es) of reference filenames. 63 | # You can specify multiple suffix as a list of string: 64 | # 65 | # source_suffix = ['.rst', '.md'] 66 | source_suffix = '.rst' 67 | 68 | # The master toctree document. 69 | master_doc = 'index' 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | # 74 | # This is also used if you do content translation via gettext catalogs. 75 | # Usually you set "language" from the command line for these cases. 76 | language = None 77 | 78 | # List of patterns, relative to reference directory, that match files and 79 | # directories to ignore when looking for reference files. 80 | # This pattern also affects html_static_path and html_extra_path. 81 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 82 | 83 | # -- Options for LaTeX output ------------------------------------------------ 84 | 85 | latex_elements = { 86 | # The paper size ('letterpaper' or 'a4paper'). 87 | # 88 | # 'papersize': 'letterpaper', 89 | 90 | # The font size ('10pt', '11pt' or '12pt'). 91 | # 92 | # 'pointsize': '10pt', 93 | 94 | # Additional stuff for the LaTeX preamble. 95 | # 96 | # 'preamble': '', 97 | 98 | # Latex figure (float) alignment 99 | # 100 | # 'figure_align': 'htbp', 101 | } 102 | 103 | # Grouping the document tree into LaTeX files. List of tuples 104 | # (reference start file, target name, title, 105 | # author, documentclass [howto, manual, or own class]). 106 | latex_documents = [ 107 | (master_doc, 'gcsfs.tex', 'gcsfs Documentation', 108 | 'Othoz GmbH', 'manual'), 109 | ] 110 | 111 | 112 | # -- Options for manual page output ------------------------------------------ 113 | 114 | # One entry per manual page. List of tuples 115 | # (reference start file, name, description, authors, manual section). 116 | man_pages = [ 117 | (master_doc, 'LEAF', 'LEAF Documentation', [author], 1) 118 | ] 119 | 120 | 121 | # -- Options for Texinfo output ---------------------------------------------- 122 | 123 | # Grouping the document tree into Texinfo files. List of tuples 124 | # (reference start file, target name, title, author, 125 | # dir menu entry, description, category) 126 | texinfo_documents = [ 127 | (master_doc, 'LEAF', 'LEAF Documentation', author, 'leaf', '-', 'Simulator'), 128 | ] 129 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to LEAF 2 | =============== 3 | 4 | What is LEAF? 5 | ------------- 6 | 7 | LEAF is a simulator for **L**\ arge **E**\ nergy-**A**\ ware **F**\ og computing environments. 8 | It enables then modeling of complex application graphs in distributed, heterogeneous, and resource-constrained infrastructures. 9 | A special emphasis was put on the modeling of energy consumption (and soon carbon emissions). 10 | 11 | .. image:: _static/infrastructure.png 12 | :alt: Alternative text 13 | 14 | What can I do with it? 15 | ---------------------- 16 | 17 | LEAF enables a high-level modeling of cloud, fog and edge computing environments. 18 | It builds on top of `networkx `_, a library for creating and manipulating complex networks, 19 | and `SimPy `_, a process-based discrete-event simulation framework. 20 | 21 | Besides allowing research on scheduling and placement algorithms on resource-constrained environments, 22 | LEAF puts a special focus on: 23 | 24 | - **Dynamic networks**: Simulate mobile nodes which can join or leave the network during the simulation. 25 | - **Power consumption modeling**: Model the power usage of individual compute nodes, network traffic and applications. 26 | - **Energy-aware algorithms**: Implement dynamically adapting task placement strategies, routing policies, and other energy-saving mechanisms. 27 | - **Scalability**: Model the execution of thousands of compute nodes and applications in magnitudes faster than real time. 28 | 29 | 30 | How does it work? 31 | ----------------- 32 | 33 | Unlike other discrete event simulators for computer networks, LEAF models infrastructure and applications on a high 34 | level based on graphs. The infrastructure graph consists of compute nodes 35 | such as data centers or sensors that are connected via network links. Applications are represented as 36 | `directed acyclic graphs (DAG) `_, where tasks are connected by their respective data flows. 37 | 38 | This allows for easy to configure and easy to analyze experiments that are fast to execute. 39 | 40 | .. image:: _static/graph-model.png 41 | :width: 450px 42 | :align: center 43 | 44 | The above example shows: 45 | 46 | - an infrastructure graph with its resource constraints and power usage characteristics (black) 47 | - a placed application with its resource requirements (orange) 48 | - and its resulting power usage on the infrastructure (green) 49 | 50 | In this configuration, the application’s combined power usage is 13.12 W. 51 | 52 | How to model change over time? 53 | .............................. 54 | 55 | The above example models the application's power usage at a specific point in time. 56 | Now, in order to enable dynamically changing components such as mobile nodes, we can **update** the parameters, quantity and placement of infrastructure as well as applications through events: 57 | 58 | .. image:: _static/des.png 59 | :width: 400px 60 | :align: center 61 | 62 | Components can **read** from via events, too. 63 | Examples for this are power meters that periodically measure the power usage of a component or placement strategies that change their behaviour based on certain system states. 64 | 65 | LEAF allows you to model different kinds of algorithms, hardware (think of batteries that update their state of charge) and energy-saving mechanisms. 66 | LEAF is *no* suitable tool if you require a low-level modeling of packet traffic for simulating effects like network congestion. 67 | 68 | For a detailed explanation of the model, please read our paper [#leaf]_. 69 | 70 | 71 | How do I use it? 72 | ---------------- 73 | 74 | To ease your start with LEAF, we provide some examples: 75 | 76 | - The `simple scenario `_ implements a data center, 77 | a fog node and a sensor that execute a simple application. Play around with the different parameters to see how resource constraints and task placement strategies affect the simulation. 78 | - The `smart city traffic scenario `_ is a lot more complicated. 79 | It simulates the traffic of taxis and the execution of two different kinds of applications in a connected city center. 80 | You can read up on the setup and results of this scenario in our paper. 81 | 82 | 83 | Publications 84 | ------------ 85 | 86 | .. [#leaf] Philipp Wiesner and Lauritz Thamsen. "`LEAF: Simulating Large Energy-Aware Fog Computing Environments `_" In the Proceedings of the 2021 *5th IEEE International Conference on Fog and Edge Computing (ICFEC)*. IEEE. 2021. `[arXiv preprint] `_ `[video] `_ 87 | 88 | 89 | .. toctree:: 90 | :hidden: 91 | 92 | self 93 | changelog 94 | reference/modules 95 | 96 | 97 | Contact 98 | ------- 99 | 100 | LEAF was developed at the research group for `Adaptive Resource Management (ARM) `_, part of the research group for `Distributed and Operating Systems (DOS) `_, at TU Berlin. 101 | 102 | In case of questions, please reach out to `Philipp Wiesner `_. 103 | -------------------------------------------------------------------------------- /docs/reference/application.rst: -------------------------------------------------------------------------------- 1 | Application 2 | =========== 3 | 4 | .. automodule:: application 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/reference/infrastructure.rst: -------------------------------------------------------------------------------- 1 | Infrastructure 2 | ============== 3 | 4 | .. automodule:: infrastructure 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/reference/modules.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | infrastructure 8 | application 9 | orchestrator 10 | power 11 | -------------------------------------------------------------------------------- /docs/reference/orchestrator.rst: -------------------------------------------------------------------------------- 1 | Orchestrator 2 | ============ 3 | 4 | .. automodule:: orchestrator 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/reference/power.rst: -------------------------------------------------------------------------------- 1 | Power 2 | ===== 3 | 4 | .. automodule:: power 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /examples/1_single_node.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import simpy 3 | 4 | from leaf.application import Task 5 | from leaf.infrastructure import Node 6 | from leaf.power import PowerModelNode, PowerMeasurement, PowerMeter 7 | 8 | logger = logging.getLogger(__name__) 9 | logging.basicConfig(level=logging.DEBUG, format='%(levelname)s\t%(message)s') 10 | 11 | 12 | def main(): 13 | """Simple example that adds and removes a task from a single node 14 | 15 | Log Output: 16 | DEBUG 0: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) 17 | DEBUG 1: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) 18 | DEBUG 2: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) 19 | INFO task has been added at 3 20 | DEBUG 3: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) 21 | DEBUG 4: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) 22 | DEBUG 5: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) 23 | DEBUG 6: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) 24 | DEBUG 7: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) 25 | INFO task has been removed at 8 26 | DEBUG 8: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) 27 | DEBUG 9: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) 28 | INFO Total power usage: 200.0 Ws 29 | """ 30 | # Initializing infrastructure and workload 31 | node = Node("node1", cu=100, power_model=PowerModelNode(max_power=30, static_power=10)) 32 | task = Task(cu=100) 33 | 34 | power_meter = PowerMeter(node, name="PowerMeter1") 35 | 36 | env = simpy.Environment() # creating SimPy simulation environment 37 | env.process(placement(env, node, task)) # registering workload placement process 38 | env.process(power_meter.run(env)) # registering power metering process 39 | 40 | env.run(until=10) # run simulation for 10 seconds 41 | 42 | logger.info(f"Total power usage: {float(PowerMeasurement.sum(power_meter.measurements))} Ws") 43 | 44 | 45 | def placement(env, node, task): 46 | """Places the task after 3 seconds and removes it after 8 seconds.""" 47 | yield env.timeout(3) 48 | task.allocate(node) 49 | logger.info(f'task has been added at {env.now}') 50 | yield env.timeout(5) 51 | task.deallocate() 52 | logger.info(f'task has been removed at {env.now}') 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /examples/2_application_placement.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | import simpy 4 | 5 | from leaf.application import Application, SourceTask, ProcessingTask, SinkTask 6 | from leaf.infrastructure import Node, Link, Infrastructure 7 | from leaf.orchestrator import Orchestrator 8 | from leaf.power import PowerModelNode, PowerModelLink, PowerMeter 9 | 10 | RANDOM_SEED = 1 11 | 12 | logger = logging.getLogger(__name__) 13 | logging.basicConfig(level=logging.DEBUG, format='%(levelname)s\t%(message)s') 14 | 15 | 16 | def main(): 17 | """Simple example that places an application in the beginning an performs power measurements on different entities. 18 | 19 | Read the explanations of :func:`create_infrastructure`, :func:`create_application` and :class:`SimpleOrchestrator` 20 | for details on the scenario setup. 21 | 22 | The power meters can be configured to periodically measure the power consumption of one or more PowerAware entities 23 | such as applications, tasks, data flows, compute nodes, network links or the entire infrastructure. 24 | 25 | The scenario is running for 5 time steps. 26 | 27 | Log Output: 28 | INFO Placing Application(tasks=3): 29 | INFO - SourceTask(id=0, cu=0.1) on Node('sensor', cu=0/1). 30 | INFO - ProcessingTask(id=1, cu=5) on Node('fog', cu=0/400). 31 | INFO - SinkTask(id=2, cu=0.5) on Node('cloud', cu=0/inf). 32 | INFO - DataFlow(bit_rate=1000) on [Link('sensor' -> 'fog', bandwidth=0/30000000.0, latency=10)]. 33 | INFO - DataFlow(bit_rate=200) on [Link('fog' -> 'cloud', bandwidth=0/1000000000.0, latency=5)]. 34 | DEBUG 0: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) 35 | DEBUG 0: infrastructure_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 36 | DEBUG 0.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 37 | DEBUG 1: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) 38 | DEBUG 1.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 39 | DEBUG 2: infrastructure_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 40 | DEBUG 2: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) 41 | DEBUG 2.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 42 | DEBUG 3: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) 43 | DEBUG 3.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 44 | DEBUG 4: infrastructure_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 45 | DEBUG 4: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) 46 | DEBUG 4.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) 47 | """ 48 | infrastructure = create_infrastructure() 49 | application = create_application(source_node=infrastructure.node("sensor"), sink_node=infrastructure.node("cloud")) 50 | orchestrator = SimpleOrchestrator(infrastructure) 51 | orchestrator.place(application) 52 | 53 | application_pm = PowerMeter(application, name="application_meter") 54 | cloud_and_fog_pm = PowerMeter([infrastructure.node("cloud"), infrastructure.node("fog")], name="cloud_and_fog_meter") 55 | infrastructure_pm = PowerMeter(infrastructure, name="infrastructure_meter", measurement_interval=2) 56 | 57 | env = simpy.Environment() 58 | env.process(application_pm.run(env, delay=0.5)) 59 | env.process(cloud_and_fog_pm.run(env)) 60 | env.process(infrastructure_pm.run(env)) 61 | env.run(until=5) 62 | 63 | 64 | def create_infrastructure(): 65 | """Create the scenario's infrastructure graph. 66 | 67 | It consists of three nodes: 68 | - A sensor with a computational capacity of one compute unit (CU). 69 | It has a maximum power usage of 1.8 Watt and a power usage of 0.2 Watt when being idle. 70 | - A fog node which can compute up to 400 CU; 200 Watt max and 30 Watt static power usage 71 | - A node representing a cloud data center with unlimited processing power that consumes 0.5 W/CU 72 | 73 | And two network links that connect the nodes: 74 | - A WiFi connection between the sensor and fog node that consumes 300 J/bit 75 | - A wide are network (WAN) connection between the fog node and cloud that consumes 6000 J/bit 76 | """ 77 | infrastructure = Infrastructure() 78 | sensor = Node("sensor", cu=1, power_model=PowerModelNode(max_power=1.8, static_power=0.2)) 79 | fog_node = Node("fog", cu=400, power_model=PowerModelNode(max_power=200, static_power=30)) 80 | cloud = Node("cloud", power_model=PowerModelNode(power_per_cu=0.5)) 81 | wifi_link_up = Link(sensor, fog_node, latency=10, bandwidth=30e6, power_model=PowerModelLink(300e-9)) 82 | wan_link_up = Link(fog_node, cloud, latency=5, bandwidth=1e9, power_model=PowerModelLink(6000e-9)) 83 | 84 | infrastructure.add_link(wifi_link_up) 85 | infrastructure.add_link(wan_link_up) 86 | return infrastructure 87 | 88 | 89 | def create_application(source_node: Node, sink_node: Node): 90 | """Create the application running in the scenario. 91 | 92 | It consists of three tasks and two data flows between these tasks: 93 | - A source task that is bound to the sensor node and requires 0.1 CU (for measuring data) 94 | - A processing task that receives 1000 bit/s from the source task, requires 5 CU (for aggregating the data) 95 | and returns 200 bit/s to the sink task 96 | - A sink task that is bound to the cloud node and requires 0.5 CU (for storing the data) 97 | """ 98 | application = Application() 99 | 100 | source_task = SourceTask(cu=0.1, bound_node=source_node) 101 | processing_task = ProcessingTask(cu=5) 102 | sink_task = SinkTask(cu=0.5, bound_node=sink_node) 103 | 104 | application.add_task(source_task) 105 | application.add_task(processing_task, incoming_data_flows=[(source_task, 1000)]) 106 | application.add_task(sink_task, incoming_data_flows=[(processing_task, 200)]) 107 | 108 | return application 109 | 110 | 111 | class SimpleOrchestrator(Orchestrator): 112 | """Very simple orchestrator that places the processing task on the fog node. 113 | 114 | You can try out other placements here and see how the placement may consume more energy ("cloud") 115 | or fail because there are not enough resources available ("sensor"). 116 | """ 117 | 118 | def _processing_task_placement(self, processing_task: ProcessingTask, application: Application) -> Node: 119 | return self.infrastructure.node("fog") 120 | 121 | 122 | if __name__ == '__main__': 123 | random.seed(RANDOM_SEED) 124 | main() 125 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/analysis/create_plots.py: -------------------------------------------------------------------------------- 1 | """Creates plots for one or more experiment runs. 2 | 3 | The follow plots are created: 4 | - A bar plot comparing all experiments in the specified directory 5 | - A plot comparing experiments Fog 4 and Fog6s 6 | - For every experiment a detailed plot on infrastructure component and application consumption. 7 | Additionally a plot for both applications that illustrates the static consumption. 8 | """ 9 | 10 | import os 11 | import warnings 12 | from typing import Tuple, Dict, List 13 | 14 | import numpy as np 15 | import pandas as pd 16 | import plotly.graph_objs as go 17 | from figures import subplot_figure, barplot_figure, timeline_figure, single_experiment_figure 18 | from scipy import signal 19 | from settings import SOURCE_DIR, RESULTS_DIR, EXPERIMENTS, EXPERIMENT_TITLES, COLORS 20 | 21 | ExperimentResults = Dict[str, Tuple[pd.DataFrame, pd.DataFrame]] 22 | 23 | 24 | def load_experiment_results() -> ExperimentResults: 25 | results = {} 26 | for experiment in EXPERIMENTS: 27 | df_i = pd.read_csv(os.path.join(SOURCE_DIR, experiment, "infrastructure.csv"), index_col="time") 28 | df_a = pd.read_csv(os.path.join(SOURCE_DIR, experiment, "applications.csv"), index_col="time") 29 | results[experiment] = (df_i, df_a) 30 | return results 31 | 32 | 33 | def create_comparison_plot(results: ExperimentResults, name: str = None): 34 | fig = timeline_figure() 35 | for result, (df, _) in results.items(): 36 | experiment_name = EXPERIMENT_TITLES[EXPERIMENTS.index(result)] 37 | series = df.drop(columns="taxis").sum(axis=1) 38 | fig.add_trace(go.Scatter(x=series.index, y=signal.savgol_filter(series, 3601, 3), name=experiment_name, line=dict(width=1))) 39 | fig.write_image(os.path.join(RESULTS_DIR, name)) 40 | 41 | 42 | def create_barplot(results: ExperimentResults): 43 | cloud = [] 44 | fog_static = [] 45 | fog_dynamic = [] 46 | wifi = [] 47 | wan = [] 48 | for _, (df, _) in results.items(): 49 | cloud.append(df["cloud dynamic"].sum() / 3600000) 50 | fog_static.append(df["fog static"].sum() / 3600000) 51 | fog_dynamic.append(df["fog dynamic"].sum() / 3600000) 52 | wifi.append(df["wifi dynamic"].sum() / 3600000) 53 | wan.append((df["wanUp dynamic"].sum() + df["wanDown dynamic"].sum()) / 3600000) 54 | total = list(np.array(cloud) + np.array(fog_static) + np.array(fog_dynamic) + np.array(wifi) + np.array(wan)) 55 | 56 | fig = barplot_figure() 57 | fig.add_trace(go.Bar(name='Fog static', x=EXPERIMENT_TITLES, y=fog_static, marker_color=COLORS["fog_static"])) 58 | fig.add_trace(go.Bar(name='Fog dynamic', x=EXPERIMENT_TITLES, y=fog_dynamic, marker_color=COLORS["fog"])) 59 | fig.add_trace(go.Bar(name='Cloud', x=EXPERIMENT_TITLES, y=cloud, marker_color=COLORS["cloud"])) 60 | fig.add_trace(go.Bar(name='Wi-Fi', x=EXPERIMENT_TITLES, y=wifi, marker_color=COLORS["wifi"])) 61 | fig.add_trace(go.Bar(name='WAN', x=EXPERIMENT_TITLES, y=wan, marker_color=COLORS["wan"], 62 | text=total, texttemplate='%{text:.2f}', textposition='outside')) 63 | fig.write_image(os.path.join(RESULTS_DIR, "barplot.pdf")) 64 | 65 | 66 | def create_infrastructure_subplot(results: ExperimentResults): 67 | fig = subplot_figure() 68 | for i, (_, (df, _)) in enumerate(results.items(), 1): 69 | fig.add_trace(go.Scatter(x=df.index, y=df["wanUp dynamic"]+df["wanDown dynamic"], name="WAN", line=dict(width=1, color=COLORS["wan"]), showlegend=(i == 1)), row=1, col=i) 70 | fig.add_trace(go.Scatter(x=df.index, y=df["wifi dynamic"], name="Wi-Fi", line=dict(width=1, color=COLORS["wifi"]), showlegend=(i == 1)), row=1, col=i) 71 | fig.add_trace(go.Scatter(x=df.index, y=df["cloud dynamic"], name="Cloud", line=dict(width=1, color=COLORS["cloud"]), showlegend=(i == 1)), row=1, col=i) 72 | fig.add_trace(go.Scatter(x=df.index, y=df["fog static"] + df["fog dynamic"], name="Fog", line=dict(width=1, color=COLORS["fog"]), showlegend=(i == 1)), row=1, col=i) 73 | fig.add_trace(go.Scatter(x=df.index, y=df["fog static"], name="Fog static", line=dict(width=1, dash="1px,2px", color=COLORS["fog"]), showlegend=(i == 1)), row=1, col=i) 74 | fig.write_image(os.path.join(RESULTS_DIR, "infrastructure.pdf")) 75 | 76 | 77 | def create_applications_subplot(results: ExperimentResults): 78 | fig = subplot_figure() 79 | for i, (_, (_, df)) in enumerate(results.items(), 1): 80 | fig.add_trace(go.Scatter(x=df.index, y=df["cctv static"] + df["cctv dynamic"], name="CCTV", line=dict(width=1, color=COLORS["cctv"]), showlegend=(i == 1)), row=1, col=i) 81 | fig.add_trace(go.Scatter(x=df.index, y=df["v2i static"] + df["v2i dynamic"], name="V2I", line=dict(width=1, color=COLORS["v2i"]), showlegend=(i == 1)), row=1, col=i) 82 | fig.write_image(os.path.join(RESULTS_DIR, "applications.pdf")) 83 | 84 | 85 | def create_taxi_count_plot(df: pd.DataFrame): 86 | fig = timeline_figure(yaxes_title="Number of cars") 87 | fig.add_trace(go.Scatter(x=df.index, y=df["taxis"], name="Taxis", line=dict(width=1, color=COLORS["wan"]))) 88 | return fig 89 | 90 | 91 | def infrastructure_figure(df: pd.DataFrame): 92 | fig = single_experiment_figure() 93 | fig.add_trace(go.Scatter(x=df.index, y=df["wanUp dynamic"] + df["wanDown dynamic"], name="WAN", line=dict(width=1, color=COLORS["wan"]))) 94 | fig.add_trace(go.Scatter(x=df.index, y=df["wifi dynamic"], name="Wi-Fi", line=dict(width=1, color=COLORS["wifi"]))) 95 | fig.add_trace(go.Scatter(x=df.index, y=df["cloud dynamic"], name="Cloud", line=dict(width=1, color=COLORS["cloud"]))) 96 | if (df["fog static"] + df["fog dynamic"]).sum() > 0: 97 | fig.add_trace(go.Scatter(x=df.index, y=df["fog static"] + df["fog dynamic"], name="Fog", line=dict(width=1, color=COLORS["fog"]))) 98 | fig.add_trace(go.Scatter(x=df.index, y=df["fog static"], name="Fog static", line=dict(width=1, dash="dot", color=COLORS["fog"]))) 99 | return fig 100 | 101 | 102 | def applications_figure(df: pd.DataFrame): 103 | fig = single_experiment_figure() 104 | fig.add_trace(go.Scatter(x=df.index, y=df["cctv static"] + df["cctv dynamic"], name="CCTV", line=dict(width=1, color=COLORS["cctv"]))) 105 | fig.add_trace(go.Scatter(x=df.index, y=df["v2i static"] + df["v2i dynamic"], name="V2I", line=dict(width=1, color=COLORS["v2i"]))) 106 | return fig 107 | 108 | 109 | def _filter_results(results: ExperimentResults, experiment_names: List[str]) -> ExperimentResults: 110 | return {k: v for k, v in results.items() if k in experiment_names} 111 | 112 | 113 | if __name__ == '__main__': 114 | warnings.filterwarnings("ignore") 115 | results = load_experiment_results() 116 | 117 | for key, (df_i, df_a) in results.items(): 118 | fig_i = infrastructure_figure(df_i) 119 | fig_a = applications_figure(df_a) 120 | os.makedirs(os.path.join(RESULTS_DIR, key), exist_ok=True) 121 | fig_i.write_image(os.path.join(RESULTS_DIR, key, "infrastructure.pdf")) 122 | fig_a.write_image(os.path.join(RESULTS_DIR, key, "applications.pdf")) 123 | 124 | create_barplot(results) 125 | 126 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/analysis/environment.yml: -------------------------------------------------------------------------------- 1 | name: leaf_analysis 2 | channels: 3 | - defaults 4 | - conda-forge 5 | - plotly 6 | dependencies: 7 | - python=3.8 8 | - pandas 9 | - numpy 10 | - scipy 11 | - plotly 12 | 13 | # Required by plotly for exporting PDFs (https://plotly.com/python/static-image-export/) 14 | - plotly-orca=1.2.1 15 | - psutil 16 | - requests 17 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/analysis/figures.py: -------------------------------------------------------------------------------- 1 | """Utility class for figure styles.""" 2 | 3 | import plotly.graph_objs as go 4 | from plotly.subplots import make_subplots 5 | 6 | 7 | def base_figure(fig: go.Figure = None) -> go.Figure: 8 | if not fig: 9 | fig = go.Figure() 10 | fig.update_layout(template="plotly_white", 11 | legend_orientation="h", 12 | legend=dict(x=0, y=1.1), 13 | xaxis=dict(mirror=True, linewidth=1, linecolor='black', ticks = '', showline=True), 14 | yaxis=dict(mirror=True, linewidth=1, linecolor='black', ticks = '', showline=True)) 15 | return fig 16 | 17 | 18 | def timeline_figure(fig: go.Figure = None, yaxes_title: str = "Power Usage (Watt)") -> go.Figure: 19 | fig = base_figure(fig) 20 | fig.update_xaxes( 21 | title=dict(text="Time", standoff=0), 22 | ticktext=[" ", "04:00", "08:00", "12:00", "16:00", "20:00", "24:00"], 23 | tickvals=[h * 120 * 60 * 2 for h in range(13)], 24 | ) 25 | fig.update_yaxes( 26 | title=dict(text=yaxes_title, standoff=0), 27 | rangemode="nonnegative", 28 | ) 29 | fig.update_layout( 30 | height=370, 31 | width=500, 32 | font=dict(size=9), 33 | legend=dict(x=0, y=1.16), 34 | ) 35 | return fig 36 | 37 | 38 | def single_experiment_figure(): 39 | fig = timeline_figure() 40 | fig.update_layout( 41 | height=450, 42 | width=600, 43 | legend=dict(x=0, y=1.12), 44 | ) 45 | return fig 46 | 47 | 48 | def barplot_figure(fig: go.Figure = None) -> go.Figure: 49 | fig = base_figure(fig) 50 | fig.update_layout(barmode='stack') 51 | fig.update_yaxes(title=dict(text="kWh consumed in 24h", standoff=0)) 52 | fig.update_layout( 53 | height=420, 54 | width=565, 55 | font=dict(size=10), 56 | legend=dict(yanchor="bottom", y=1.01, x=-0.02), 57 | ) 58 | # Fine tuning: Make chart a little higher than plotly suggests so the labels aren't cut off 59 | fig.update_yaxes(range=[0, 70]) 60 | return fig 61 | 62 | 63 | def subplot_figure(): 64 | fig = make_subplots(rows=1, cols=3, shared_yaxes=True, horizontal_spacing=0.01) 65 | fig = timeline_figure(fig) 66 | fig.update_layout( 67 | height=320, 68 | width=1000, 69 | font=dict(size=9), 70 | legend=dict(x=0, y=1.215), 71 | ) 72 | fig.update_yaxes(title_text=None) 73 | fig.update_yaxes(title=dict(text="Power Usage (Watt)", standoff=0), row=1, col=1) 74 | 75 | fig.update_xaxes(showline=True, linewidth=1, linecolor='black', mirror=True) 76 | fig.update_yaxes(showline=True, linewidth=1, linecolor='black', mirror=True) 77 | return fig 78 | 79 | 80 | def taxi_figure() -> go.Figure: 81 | # Create figure with secondary y-axis 82 | fig = make_subplots(specs=[[{"secondary_y": True}]]) 83 | fig = timeline_figure(fig=fig) 84 | 85 | fig.update_yaxes(title_text="Taxis generated per minute", secondary_y=False) 86 | fig.update_yaxes(title_text="Driving speed (km/h)", secondary_y=True) 87 | # fig.update_xaxes(range=[0, 86400]) 88 | return fig 89 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/analysis/settings.py: -------------------------------------------------------------------------------- 1 | """Settings for generating plots.""" 2 | 3 | SOURCE_DIR = "../results" 4 | RESULTS_DIR = "../results" 5 | EXPERIMENTS = ["fog_0", "fog_1", "fog_2", "fog_3", "fog_4", "fog_5", "fog_6", "fog_6_shutdown"] 6 | EXPERIMENT_TITLES = ["Cloud only", "Fog 1", "Fog 2", "Fog 3", "Fog 4", "Fog 5", "Fog 6", "Fog 6s"] 7 | 8 | COLORS = { 9 | "cloud": "#34495e", # '#e48064', 10 | "fog": '#e74c3c', 11 | "fog_static": '#c0392b', 12 | "wan": '#3498db', 13 | "wifi": '#2ecc71', 14 | "cctv": '#0c6f68', 15 | "v2i": '#c9bc02', 16 | } -------------------------------------------------------------------------------- /examples/smart_city_traffic/city.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple, Iterator 2 | 3 | import networkx as nx 4 | import simpy 5 | 6 | from examples.smart_city_traffic.infrastructure import Cloud, FogNode, TrafficLight, LinkWanUp, LinkEthernet, \ 7 | LinkWifiBetweenTrafficLights, LinkWanDown, LinkWifiTaxiToTrafficLight, Taxi 8 | from mobility import Location 9 | from examples.smart_city_traffic.orchestrator import CityOrchestrator 10 | from examples.smart_city_traffic.settings import * 11 | from leaf.infrastructure import Infrastructure 12 | 13 | 14 | class City: 15 | def __init__(self, env: simpy.Environment): 16 | self.env = env 17 | self.street_graph, self.entry_point_locations, self.traffic_light_locations = _create_street_graph() 18 | self.infrastructure = Infrastructure() 19 | self.orchestrator = CityOrchestrator(self.infrastructure, utilization_threshold=FOG_UTILIZATION_THRESHOLD) 20 | 21 | # Create infrastructure 22 | self.infrastructure.add_node(Cloud()) 23 | for location in self.traffic_light_locations: 24 | self._add_traffic_light(location) 25 | for location in RNG.choice(self.traffic_light_locations, FOG_DCS): 26 | self._add_fog_node(location) 27 | 28 | # Start update wifi connections process 29 | self.update_wifi_connections_process = self.env.process(self._update_wifi_connections()) 30 | 31 | # Place CCTV applications 32 | for traffic_light in self.infrastructure.nodes(type_filter=TrafficLight): 33 | self.orchestrator.place(traffic_light.application) 34 | 35 | def add_taxi_and_start_v2i_app(self, taxi: Taxi): 36 | """Cars are connected to all traffic light systems in range via WiFi. 37 | 38 | Note: This initial allocation may change during simulation since taxis are mobile. 39 | """ 40 | self.infrastructure.add_link(LinkWifiTaxiToTrafficLight(taxi, self._closest_traffic_light(taxi))) 41 | self.orchestrator.place(taxi.application) 42 | 43 | def remove_taxi_and_stop_v2i_app(self, taxi: Taxi): 44 | taxi.application.deallocate() 45 | self.infrastructure.remove_node(taxi) 46 | 47 | def _add_traffic_light(self, location: Location): 48 | """Traffic lights are connected to the cloud via WAN and to other traffic lights in range via WiFi.""" 49 | cloud: Cloud = self.infrastructure.nodes(type_filter=Cloud)[0] 50 | traffic_light = TrafficLight(location, application_sink=cloud) 51 | self.infrastructure.add_link(LinkWanUp(traffic_light, cloud)) 52 | self.infrastructure.add_link(LinkWanDown(cloud, traffic_light)) 53 | for traffic_light_ in self._traffic_lights_in_range(traffic_light): 54 | self.infrastructure.add_link(LinkWifiBetweenTrafficLights(traffic_light, traffic_light_)) 55 | self.infrastructure.add_link(LinkWifiBetweenTrafficLights(traffic_light_, traffic_light)) 56 | 57 | def _add_fog_node(self, location: Location): 58 | """Fog nodes are connected to a traffic lights via Ethernet (no power usage)""" 59 | fog_node = FogNode(location) 60 | for traffic_light in self.infrastructure.nodes(type_filter=TrafficLight): 61 | if traffic_light.location == location: 62 | self.infrastructure.add_link(LinkEthernet(traffic_light, fog_node)) 63 | self.infrastructure.add_link(LinkEthernet(fog_node, traffic_light)) 64 | 65 | def _update_wifi_connections(self): 66 | """Recalculates the traffic lights in range for all taxis.""" 67 | g = self.infrastructure.graph 68 | while True: 69 | yield self.env.timeout(UPDATE_WIFI_CONNECTIONS_INTERVAL) 70 | for taxi in self.infrastructure.nodes(type_filter=Taxi): 71 | tl_connected_name = next(g.neighbors(taxi.name)) 72 | tl_closest = self._closest_traffic_light(taxi) 73 | if tl_connected_name != tl_closest.name: 74 | g.remove_edge(taxi.name, tl_connected_name) 75 | self.infrastructure.add_link(LinkWifiTaxiToTrafficLight(taxi, tl_closest)) 76 | 77 | def _traffic_lights_in_range(self, traffic_light: TrafficLight) -> Iterator[TrafficLight]: 78 | for tl in self.infrastructure.nodes(type_filter=TrafficLight): 79 | if traffic_light.location.distance(tl.location) <= WIFI_RANGE: 80 | yield tl 81 | 82 | def _closest_traffic_light(self, taxi: Taxi) -> TrafficLight: 83 | return min(self.infrastructure.nodes(type_filter=TrafficLight), key=lambda tl: taxi.location.distance(tl.location)) 84 | 85 | 86 | def _create_street_graph() -> Tuple[nx.Graph, List[Location], List[Location]]: 87 | graph = nx.Graph() 88 | n_points = STREETS_PER_AXIS + 2 # crossings + entry points 89 | step_size_x = CITY_WIDTH / (n_points - 1) 90 | step_size_y = CITY_HEIGHT / (n_points - 1) 91 | 92 | entry_point_locations = [] 93 | traffic_light_locations = [] 94 | locations = [[None for _ in range(n_points)] for _ in range(n_points)] 95 | for x in range(n_points): 96 | for y in range(n_points): 97 | location = Location(x * step_size_x, y * step_size_y) 98 | if x == 0 or x == n_points - 1 or y == 0 or y == n_points - 1: 99 | entry_point_locations.append(location) 100 | else: 101 | traffic_light_locations.append(location) 102 | locations[x][y] = location 103 | graph.add_node(location) 104 | if x > 0 and y > 0: 105 | if y < n_points - 1: 106 | graph.add_edge(location, locations[x - 1][y]) 107 | if x < n_points - 1: 108 | graph.add_edge(location, locations[x][y - 1]) 109 | 110 | for corner_location in [locations[0][0], 111 | locations[n_points - 1][0], 112 | locations[0][n_points - 1], 113 | locations[n_points - 1][n_points - 1]]: 114 | graph.remove_node(corner_location) 115 | entry_point_locations.remove(corner_location) 116 | 117 | return graph, entry_point_locations, traffic_light_locations 118 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/infrastructure.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import simpy 4 | 5 | from leaf.application import Application, SourceTask, ProcessingTask, SinkTask 6 | from examples.smart_city_traffic.settings import * 7 | from leaf.infrastructure import Link, Node 8 | from leaf.power import PowerModelLink, PowerModelNode, PowerMeasurement 9 | 10 | """Counter for incrementally naming nodes""" 11 | _fog_nodes_created = 0 12 | _traffic_lights_created = 0 13 | _taxis_created = 0 14 | 15 | 16 | class Cloud(Node): 17 | def __init__(self): 18 | super().__init__("cloud", cu=CLOUD_CU, power_model=PowerModelNode(power_per_cu=CLOUD_WATT_PER_CU)) 19 | 20 | 21 | class FogNode(Node): 22 | def __init__(self, location: "Location"): 23 | # TODO Shutdown! 24 | global _fog_nodes_created 25 | super().__init__(f"fog_{_fog_nodes_created}", cu=FOG_CU, 26 | power_model=PowerModelNode(max_power=FOG_MAX_POWER, static_power=FOG_STATIC_POWER), 27 | location=location) 28 | _fog_nodes_created += 1 29 | self.shutdown = FOG_IDLE_SHUTDOWN 30 | 31 | def measure_power(self) -> PowerMeasurement: 32 | if self.shutdown: 33 | return PowerMeasurement(0, 0) 34 | else: 35 | return super().measure_power() 36 | 37 | def add_task(self, task: "Task"): 38 | if self.shutdown: 39 | self.shutdown = False 40 | super().add_task(task) 41 | 42 | def remove_task(self, task: "Task"): 43 | super().remove_task(task) 44 | if FOG_IDLE_SHUTDOWN and self.used_cu == 0: 45 | self.shutdown = True 46 | 47 | 48 | class TrafficLight(Node): 49 | def __init__(self, location: "Location", application_sink: Node): 50 | global _traffic_lights_created 51 | super().__init__(f"traffic_light_{_traffic_lights_created}", location=location) 52 | _traffic_lights_created += 1 53 | self.application = self._create_cctv_application(application_sink) 54 | 55 | def _create_cctv_application(self, application_sink: Node): 56 | application = Application() 57 | source_task = SourceTask(cu=0, bound_node=self) 58 | application.add_task(source_task) 59 | processing_task = ProcessingTask(cu=CCTV_PROCESSOR_CU) 60 | application.add_task(processing_task, incoming_data_flows=[(source_task, CCTV_SOURCE_TO_PROCESSOR_BIT_RATE)]) 61 | sink_task = SinkTask(cu=0, bound_node=application_sink) 62 | application.add_task(sink_task, incoming_data_flows=[(processing_task, CCTV_PROCESSOR_TO_SINK_BIT_RATE)]) 63 | return application 64 | 65 | 66 | class Taxi(Node): 67 | def __init__(self, env: simpy.Environment, mobility_model: "TaxiMobilityModel", application_sinks: List[Node]): 68 | global _taxis_created 69 | super().__init__(f"taxi_{_taxis_created}") 70 | _taxis_created += 1 71 | self.env = env 72 | self.application = self._create_v2i_application(application_sinks) 73 | self.mobility_model = mobility_model 74 | 75 | @property 76 | def location(self) -> "Location": 77 | return self.mobility_model.location(self.env.now) 78 | 79 | @location.setter 80 | def location(self, value): 81 | pass # only for initialization, locations of this node is managed by the TaxiMobilityModel 82 | 83 | def _create_v2i_application(self, application_sinks: List[Node]) -> Application: 84 | application = Application() 85 | source_task = SourceTask(cu=0, bound_node=self) 86 | application.add_task(source_task) 87 | processing_task = ProcessingTask(cu=V2I_PROCESSOR_CU) 88 | application.add_task(processing_task, incoming_data_flows=[(source_task, V2I_SOURCE_TO_PROCESSOR_BIT_RATE)]) 89 | for application_sink in application_sinks: 90 | sink_task = SinkTask(cu=0, bound_node=application_sink) 91 | application.add_task(sink_task, incoming_data_flows=[(processing_task, V2I_PROCESSOR_TO_SINK_BIT_RATE)]) 92 | return application 93 | 94 | 95 | class LinkEthernet(Link): 96 | def __init__(self, src: Node, dst: Node): 97 | super().__init__(src, dst, 98 | bandwidth=ETHERNET_BANDWIDTH, 99 | latency=ETHERNET_LATENCY, 100 | power_model=PowerModelLink(ETHERNET_WATT_PER_BIT)) 101 | 102 | 103 | class LinkWanUp(Link): 104 | def __init__(self, src: Node, dst: Node): 105 | super().__init__(src, dst, 106 | bandwidth=WAN_BANDWIDTH, 107 | latency=WAN_LATENCY, 108 | power_model=PowerModelLink(WAN_WATT_PER_BIT_UP)) 109 | 110 | 111 | class LinkWanDown(Link): 112 | def __init__(self, src: Node, dst: Node): 113 | super().__init__(src, dst, 114 | bandwidth=WAN_BANDWIDTH, 115 | latency=WAN_LATENCY, 116 | power_model=PowerModelLink(WAN_WATT_PER_BIT_DOWN)) 117 | 118 | 119 | class LinkWifiBetweenTrafficLights(Link): 120 | def __init__(self, src: Node, dst: Node): 121 | super().__init__(src, dst, 122 | bandwidth=WIFI_BANDWIDTH, 123 | latency=WIFI_LATENCY, 124 | power_model=PowerModelLink(WIFI_TL_TO_TL_WATT_PER_BIT)) 125 | 126 | 127 | class LinkWifiTaxiToTrafficLight(Link): 128 | def __init__(self, src: Node, dst: Node): 129 | super().__init__(src, dst, 130 | bandwidth=WIFI_BANDWIDTH, 131 | latency=WIFI_LATENCY, 132 | power_model=PowerModelLink(WIFI_TAXI_TO_TL_WATT_PER_BIT)) 133 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from os import makedirs 3 | 4 | import simpy 5 | from tqdm import tqdm 6 | 7 | from examples.smart_city_traffic.city import City 8 | from examples.smart_city_traffic.infrastructure import Cloud, FogNode, Taxi, LinkWanDown, LinkWanUp, \ 9 | LinkWifiTaxiToTrafficLight, LinkWifiBetweenTrafficLights, TrafficLight 10 | from examples.smart_city_traffic.mobility import MobilityManager 11 | from examples.smart_city_traffic.settings import SIMULATION_TIME, FOG_DCS, POWER_MEASUREMENT_INTERVAL, \ 12 | FOG_IDLE_SHUTDOWN 13 | from leaf.infrastructure import Infrastructure 14 | from leaf.power import PowerMeter 15 | 16 | logger = logging.getLogger(__name__) 17 | logging.basicConfig(level=logging.WARN, format='%(levelname)s: %(message)s') 18 | 19 | 20 | def main(count_taxis: bool, measure_infrastructure: bool, measure_applications: bool): 21 | # ----------------- Set up experiment ----------------- 22 | env = simpy.Environment() 23 | city = City(env) 24 | mobility_manager = MobilityManager(city) 25 | env.process(mobility_manager.run(env)) 26 | 27 | # ----------------- Initialize meters ----------------- 28 | if count_taxis: 29 | # Measures the amount of taxis on the map 30 | taxi_counter = TaxiCounter(env, city.infrastructure) 31 | if measure_infrastructure: 32 | # Measures the power usage of cloud and fog nodes as well as WAN and WiFi links 33 | pm_cloud = PowerMeter(entities=city.infrastructure.nodes(type_filter=Cloud), name="cloud", measurement_interval=POWER_MEASUREMENT_INTERVAL) 34 | pm_fog = PowerMeter(entities=city.infrastructure.nodes(type_filter=FogNode), name="fog", measurement_interval=POWER_MEASUREMENT_INTERVAL) 35 | pm_wan_up = PowerMeter(entities=city.infrastructure.links(type_filter=LinkWanUp), name="wan_up", measurement_interval=POWER_MEASUREMENT_INTERVAL) 36 | pm_wan_down = PowerMeter(entities=city.infrastructure.links(type_filter=LinkWanDown), name="wan_down", measurement_interval=POWER_MEASUREMENT_INTERVAL) 37 | pm_wifi = PowerMeter(entities=lambda: city.infrastructure.links(type_filter=(LinkWifiBetweenTrafficLights, LinkWifiTaxiToTrafficLight)), name="wifi", measurement_interval=POWER_MEASUREMENT_INTERVAL) 38 | 39 | env.process(pm_cloud.run(env)) 40 | env.process(pm_fog.run(env)) 41 | env.process(pm_wan_up.run(env)) 42 | env.process(pm_wan_down.run(env)) 43 | env.process(pm_wifi.run(env)) 44 | if measure_applications: 45 | # Measures the power usage of the V2I and CCTV applications 46 | pm_v2i = PowerMeter(entities=lambda: [taxi.application for taxi in city.infrastructure.nodes(type_filter=Taxi)], name="v2i", measurement_interval=POWER_MEASUREMENT_INTERVAL) 47 | pm_cctv = PowerMeter(entities=lambda: [tl.application for tl in city.infrastructure.nodes(type_filter=TrafficLight)], name="cctv", measurement_interval=POWER_MEASUREMENT_INTERVAL) 48 | env.process(pm_v2i.run(env)) 49 | env.process(pm_cctv.run(env)) 50 | 51 | # ------------------ Run experiment ------------------- 52 | for until in tqdm(range(1, SIMULATION_TIME)): 53 | env.run(until=until) 54 | 55 | # ------------------ Write results -------------------- 56 | result_dir = f"results/fog_{FOG_DCS}" 57 | if FOG_IDLE_SHUTDOWN: 58 | result_dir += "_shutdown" 59 | makedirs(result_dir, exist_ok=True) 60 | if count_taxis: 61 | csv_content = "time,taxis\n" 62 | for i, taxis in enumerate(taxi_counter.measurements): 63 | csv_content += f"{i},{taxis}\n" 64 | with open(f"{result_dir}/taxis.csv", 'w') as csvfile: 65 | csvfile.write(csv_content) 66 | if measure_infrastructure: 67 | csv_content = "time,cloud static,cloud dynamic,fog static,fog dynamic,wifi static,wifi dynamic,wanUp static," \ 68 | "wanUp dynamic,wanDown static,wanDown dynamic\n" 69 | for i, (cloud, fog, wifi, wan_up, wan_down) in enumerate(zip(pm_cloud.measurements, pm_fog.measurements, pm_wifi.measurements, pm_wan_up.measurements, pm_wan_down.measurements)): 70 | csv_content += f"{i},{cloud.static},{cloud.dynamic},{fog.static},{fog.dynamic},{wifi.static},{wifi.dynamic},{wan_up.static},{wan_up.dynamic},{wan_down.static},{wan_down.dynamic}\n" 71 | with open(f"{result_dir}/infrastructure.csv", 'w') as csvfile: 72 | csvfile.write(csv_content) 73 | if measure_applications: 74 | csv_content = "time,v2i static,v2i dynamic,cctv static,cctv dynamic\n" 75 | for i, (v2i, cctv) in enumerate(zip(pm_v2i.measurements, pm_cctv.measurements)): 76 | csv_content += f"{i},{v2i.static},{v2i.dynamic},{cctv.static},{cctv.dynamic}\n" 77 | with open(f"{result_dir}/applications.csv", 'w') as csvfile: 78 | csvfile.write(csv_content) 79 | 80 | 81 | class TaxiCounter: 82 | def __init__(self, env: simpy.Environment, infrastructure: Infrastructure): 83 | self.env = env 84 | self.measurements = [] 85 | self.process = env.process(self._run(infrastructure)) 86 | 87 | def _run(self, infrastructure: Infrastructure): 88 | yield self.env.timeout(0.01) 89 | while True: 90 | self.measurements.append(len(infrastructure.nodes(type_filter=Taxi))) 91 | yield self.env.timeout(1) 92 | 93 | 94 | if __name__ == '__main__': 95 | main(count_taxis=True, measure_infrastructure=True, measure_applications=False) 96 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/mobility.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import networkx as nx 4 | import simpy 5 | 6 | from examples.smart_city_traffic.infrastructure import TrafficLight, Taxi 7 | from examples.smart_city_traffic.settings import UPDATE_MOBILITY_INTERVAL, MAX_CARS_PER_MINUTE, RNG, \ 8 | TAXI_COUNT_DISTRIBUTION, TAXI_SPEED_DISTRIBUTION 9 | from leaf.mobility import Location 10 | 11 | 12 | class MobilityManager: 13 | 14 | def __init__(self, city: "City"): 15 | self.city = city 16 | 17 | def run(self, env: simpy.Environment): 18 | while True: 19 | for taxi in self._create_taxis(env): 20 | self.city.add_taxi_and_start_v2i_app(taxi) 21 | env.process(self._remove_taxi_process(env, taxi)) 22 | yield env.timeout(UPDATE_MOBILITY_INTERVAL) 23 | 24 | def _remove_taxi_process(self, env: simpy.Environment, taxi: "Taxi"): 25 | yield env.timeout(taxi.mobility_model.life_time) 26 | self.city.remove_taxi_and_stop_v2i_app(taxi) 27 | 28 | def _create_taxis(self, env: simpy.Environment) -> List["Taxi"]: 29 | avg_taxi_speed = _avg_taxi_speed(env.now) 30 | avg_taxi_count = _avg_taxi_count(env.now) 31 | taxi_count = RNG.poisson(avg_taxi_count) 32 | return [self._create_taxi(env=env, speed=avg_taxi_speed) for _ in range(taxi_count)] 33 | 34 | def _create_taxi(self, env: simpy.Environment, speed: float) -> "Taxi": 35 | start = self._random_gate_location() 36 | dst = self._random_gate_location() 37 | while not start.distance(dst) > 0.5: 38 | dst = self._random_gate_location() 39 | path = nx.shortest_path(self.city.street_graph, source=start, target=dst) 40 | mobility_model = TaxiMobilityModel(path, speed=speed, start_time=env.now) 41 | return Taxi(env, mobility_model, application_sinks=self._traffic_lights_on_taxi_path(path)) 42 | 43 | def _random_gate_location(self) -> Location: 44 | return RNG.choice(self.city.entry_point_locations) 45 | 46 | def _traffic_lights_on_taxi_path(self, path: List) -> List[TrafficLight]: 47 | return [tl for tl in self.city.infrastructure.nodes(type_filter=TrafficLight) if tl.location in path] 48 | 49 | 50 | class TaxiMobilityModel: 51 | def __init__(self, path: List[Location], speed: float, start_time: float): 52 | self.start_time = start_time 53 | self._location_dict, self.life_time = self._create_location_dict(path, speed, interval=UPDATE_MOBILITY_INTERVAL) 54 | 55 | def location(self, time: float) -> Optional[Location]: 56 | try: 57 | return self._location_dict[self._time_to_map_key(time - self.start_time)] 58 | except KeyError: 59 | print(f"Error: {time}") 60 | return self._location_dict[max(self._location_dict.keys())] 61 | 62 | def _create_location_dict(self, path: List[Location], speed: float, interval: float): 63 | """Computes the random path of the taxi and precomputes its location at specific time steps.""" 64 | distance_per_interval = speed * UPDATE_MOBILITY_INTERVAL 65 | 66 | last_location: Location = None 67 | remaining_meters_from_last_path = 0 68 | time = 0 69 | 70 | time_location_dict = {} 71 | for next_location in path: 72 | if last_location is not None: 73 | distance = next_location.distance(last_location) 74 | for fraction in self._get_fractions(distance, remaining_meters_from_last_path, distance_per_interval): 75 | new_x = last_location.x + fraction * (next_location.x - last_location.x) 76 | new_y = last_location.y + fraction * (next_location.y - last_location.y) 77 | time_location_dict[self._time_to_map_key(time)] = Location(new_x, new_y) 78 | time += interval 79 | remaining_meters_from_last_path = distance % distance_per_interval 80 | last_location = next_location 81 | return time_location_dict, time - interval # TODO Check if this is correct 82 | 83 | @staticmethod 84 | def _get_fractions(path_distance: float, remaining_last_path_distance: float, distance_per_step: float): 85 | total_distance = path_distance + remaining_last_path_distance 86 | n_steps = int(total_distance / distance_per_step) 87 | fractions = [] 88 | for i in range(n_steps): 89 | distance_on_path = i * distance_per_step - remaining_last_path_distance 90 | fractions.append(distance_on_path / path_distance) 91 | return fractions 92 | 93 | @staticmethod 94 | def _time_to_map_key(time: float) -> int: 95 | return int(round(time * 100) / 100) 96 | 97 | 98 | def _avg_taxi_count(time: float): 99 | """Returns the average number of taxis that should be generated at this time step. 100 | The result will be passed to a Poisson distribution to determine the actual number. 101 | """ 102 | # TODO Check int 103 | steps_per_minute = 60 / UPDATE_MOBILITY_INTERVAL 104 | minute = (time / steps_per_minute) % len(TAXI_COUNT_DISTRIBUTION) 105 | return TAXI_COUNT_DISTRIBUTION[int(minute)] / steps_per_minute * MAX_CARS_PER_MINUTE 106 | 107 | 108 | def _avg_taxi_speed(time: float): 109 | """Returns the average speed of taxis at the time of the day.""" 110 | # TODO Check int 111 | steps_per_minute = 60 / UPDATE_MOBILITY_INTERVAL 112 | minute = (time / steps_per_minute) % len(TAXI_SPEED_DISTRIBUTION) 113 | return TAXI_SPEED_DISTRIBUTION[int(minute)] 114 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/orchestrator.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from leaf.application import Application, ProcessingTask 4 | from examples.smart_city_traffic.infrastructure import FogNode, Cloud 5 | from examples.smart_city_traffic.settings import FOG_UTILIZATION_THRESHOLD, FOG_DCS, FOG_IDLE_SHUTDOWN 6 | from leaf.infrastructure import Infrastructure, Node 7 | from leaf.orchestrator import Orchestrator 8 | 9 | 10 | class CityOrchestrator(Orchestrator): 11 | 12 | def __init__(self, infrastructure: Infrastructure, utilization_threshold: float = FOG_UTILIZATION_THRESHOLD): 13 | super().__init__(infrastructure) 14 | self.utilization_threshold = utilization_threshold 15 | 16 | def _processing_task_placement(self, processing_task: ProcessingTask, application: Application) -> Node: 17 | result_node = None 18 | 19 | if FOG_DCS > 0: 20 | if FOG_IDLE_SHUTDOWN: 21 | highest_utilization_below_threshold = -1 22 | for fog_node in self.infrastructure.nodes(type_filter=FogNode): 23 | if highest_utilization_below_threshold < fog_node.utilization() < self.utilization_threshold: 24 | highest_utilization_below_threshold = fog_node.utilization() 25 | result_node = fog_node 26 | else: 27 | lowest_utilization = math.inf 28 | for fog_node in self.infrastructure.nodes(type_filter=FogNode): 29 | if fog_node.utilization() < lowest_utilization: 30 | lowest_utilization = fog_node.utilization() 31 | result_node = fog_node 32 | 33 | if result_node is None or result_node.utilization() > self.utilization_threshold: 34 | result_node = self.infrastructure.nodes(type_filter=Cloud)[0] 35 | 36 | return result_node 37 | -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_0/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_0/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_0/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_0/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_1/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_1/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_1/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_1/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_2/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_2/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_2/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_2/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_3/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_3/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_3/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_3/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_4/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_4/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_4/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_4/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_5/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_5/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_5/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_5/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_6/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_6/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_6/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_6/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_6_shutdown/applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_6_shutdown/applications.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/results/fog_6_shutdown/infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dos-group/leaf/413129e88909811cb5c24c78bc2b6796e256f8fe/examples/smart_city_traffic/results/fog_6_shutdown/infrastructure.pdf -------------------------------------------------------------------------------- /examples/smart_city_traffic/settings.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | RNG = np.random.default_rng(seed=0) # Random Number Generator 4 | 5 | """The following two parameters were altered in the different experiments""" 6 | FOG_DCS = 0 7 | FOG_IDLE_SHUTDOWN = False 8 | 9 | # TODO Logging 10 | 11 | """Simulation duration and intervals in simulated seconds""" 12 | SIMULATION_TIME = 3600 * 24 13 | UPDATE_MOBILITY_INTERVAL = 1 14 | POWER_MEASUREMENT_INTERVAL = 1 15 | UPDATE_WIFI_CONNECTIONS_INTERVAL = 60 16 | 17 | """City scenario parameters""" 18 | STREETS_PER_AXIS = 4 19 | BLOCK_SIZE_WIDTH = 274 # Manhattan 20 | BLOCK_SIZE_HEIGHT = 80 # Manhattan 21 | CITY_WIDTH = (STREETS_PER_AXIS + 1) * BLOCK_SIZE_WIDTH 22 | CITY_HEIGHT = (STREETS_PER_AXIS + 1) * BLOCK_SIZE_HEIGHT 23 | 24 | """Taxi generation rate and speed distribution according to 2015 DEBS Grand Challenge dataset""" 25 | MAX_CARS_PER_MINUTE = 75 # This parameter can be adapted to scale the simulation 26 | TAXI_COUNT_DISTRIBUTION = [0.7061738625112995, 0.7029400370194998, 0.7053398476174078, 0.7111026214971374, 0.7048663423873273, 0.7015787094830184, 0.7053183246524041, 0.6968651801472171, 0.6924744952864706, 0.6902414876673411, 0.6890953897808962, 0.6827730188110714, 0.6834402307261849, 0.672259050406784, 0.6702574146614437, 0.6693265464250355, 0.6647582971030089, 0.6589901424820284, 0.6571391674917136, 0.6553473806551591, 0.6474592139813181, 0.6434505617493866, 0.6423421290516982, 0.638753174637338, 0.6380052516034609, 0.6276580861779518, 0.623670956911024, 0.6259362489776592, 0.6209106366493048, 0.6199959106366493, 0.6154922302096337, 0.6130978003529767, 0.6063987774955878, 0.6052365373853902, 0.6013516422022298, 0.5962668417201153, 0.5947548534286083, 0.5912681330980156, 0.5874101416211097, 0.5857421118333261, 0.586199474839654, 0.5807487839524773, 0.5804474624424261, 0.5783543540958203, 0.5726507683698506, 0.5721180749860101, 0.5678403856915328, 0.5644558994447075, 0.5647464594722569, 0.5577030691748095, 0.5541302569842022, 0.5523815160776548, 0.5486795660970255, 0.5468339718479618, 0.5466886918341871, 0.5400542378718092, 0.539876673410529, 0.5352869011234987, 0.5342753217683268, 0.5300083939563515, 0.5297393568938057, 0.5299115406138349, 0.5250957771942663, 0.526693857345788, 0.5239927252378288, 0.5203445826697086, 0.5207373767810254, 0.516405880074039, 0.5153673970126125, 0.511923722612027, 0.5084100985751797, 0.504229262623219, 0.5015496534802635, 0.49490981877663465, 0.4952165210279368, 0.4927306185700142, 0.48950217381946537, 0.4834434591709354, 0.48196375532693386, 0.48073156558047436, 0.4790635357926908, 0.4741024923593474, 0.4760664629159313, 0.4698140415823684, 0.4708040979725367, 0.46645107830054666, 0.46687077611811806, 0.46201196676854206, 0.4583423012354182, 0.4572876759502389, 0.4560393439800267, 0.4494802203951616, 0.44758619947483963, 0.4500559597090095, 0.4430018079290603, 0.44195794412638284, 0.4362328354354096, 0.43453790194137143, 0.4334886573974431, 0.429958891136843, 0.42814020059403385, 0.42555744479359475, 0.42357733201325815, 0.42204382075674746, 0.4200314235289053, 0.4198538590676252, 0.4171634884421678, 0.41338082734277476, 0.41468834746674704, 0.4126275235676467, 0.41012547888597134, 0.40929684473333044, 0.4063858637165856, 0.40454026946752186, 0.403770823468641, 0.4001657268305282, 0.3994124230554001, 0.397023373939994, 0.3976959665963583, 0.3943276225732857, 0.3903835392363652, 0.390900090396453, 0.39136283414403167, 0.3869452455770307, 0.3874833197021222, 0.3855623950755456, 0.38477680685291205, 0.38629955662692095, 0.38174206878739614, 0.3797135293358013, 0.37561878524385517, 0.37574792303387716, 0.3714702337393999, 0.37271318496836126, 0.36844625715638585, 0.36719792518617367, 0.3641147604493995, 0.3644322241832035, 0.362382161766605, 0.35897615255477594, 0.3594819422323619, 0.3558122766992381, 0.35240626748740905, 0.3503239206233051, 0.3469448151177306, 0.34571800611252207, 0.3462776032026172, 0.3422958546769403, 0.3414564590417976, 0.3413542249580302, 0.3375446601523826, 0.33749085273987345, 0.3361241444621411, 0.33155051439886357, 0.3299040075760837, 0.3281229822220309, 0.32869872153587876, 0.32637962205673454, 0.3204285222332228, 0.32024557703069173, 0.31614545219749474, 0.3173399767551978, 0.3170332745038957, 0.3128901037406913, 0.3107324264990745, 0.30924196117257113, 0.3094571908226077, 0.3058305712194912, 0.30676143945589945, 0.3010793766949335, 0.3023223279238948, 0.29978799879471396, 0.2997557143472085, 0.29970728767595023, 0.29638737032413587, 0.2955318324652404, 0.29151241875080713, 0.2913994231845379, 0.29079678016443544, 0.28989281563428176, 0.29229262623218977, 0.2881064095389781, 0.2890641814816409, 0.28879514441909515, 0.2847757307046619, 0.28293551719684906, 0.2821875941629719, 0.28112220739529076, 0.27943265464250355, 0.2770597477508502, 0.27574684688562695, 0.2743155697128836, 0.27055443157849424, 0.27111402866858936, 0.26901553958073265, 0.26683095863286127, 0.2639522620636219, 0.2620636218845508, 0.2585230941414489, 0.25970147647539926, 0.25506865825836167, 0.2540570789031897, 0.25093086823640826, 0.2514366579139942, 0.2494888295811631, 0.24520037880418408, 0.2447753002453618, 0.24546403512547887, 0.24311265119882916, 0.2418212732986096, 0.24120248805475442, 0.23996491756704402, 0.2399541560845422, 0.23697860617278638, 0.23518143859498084, 0.23733373509534675, 0.23595088459386165, 0.23452498816236925, 0.23266863243080366, 0.23046790925917954, 0.229870646980328, 0.22877835650639233, 0.22814342903878437, 0.2270188541173432, 0.22899896689767982, 0.22782596530498042, 0.2274600748999182, 0.22549610434333434, 0.22545305841332702, 0.22532392062330506, 0.22600189402092033, 0.22345680340923765, 0.2242101071843657, 0.22446300202315872, 0.22298329817915716, 0.22472127760320262, 0.22384421677930352, 0.22398949679307822, 0.22346218415048857, 0.22593194438465844, 0.2219017691877233, 0.2274546941586673, 0.23259330205329087, 0.23555809048254486, 0.23471331410615126, 0.23439585037234728, 0.23532671860875554, 0.2325717790882872, 0.23221665016572682, 0.2320229434806939, 0.22790667642374413, 0.2254799621195816, 0.22218156773277087, 0.2157623434204296, 0.2158322930566915, 0.2122218156773277, 0.209805862855667, 0.2081701175153889, 0.20525375575739313, 0.20136347983298178, 0.19996986784899487, 0.19634862898712926, 0.19487430588437862, 0.19192027893762645, 0.1872659377555852, 0.1853988205415178, 0.18299362920235893, 0.18091128233825493, 0.17961990443803538, 0.17642374413499204, 0.17379256166329474, 0.1725388489518316, 0.17232900004304594, 0.16728186474968793, 0.16681912100210924, 0.16382742886660065, 0.16352610735654943, 0.16279970728767595, 0.15923227583831948, 0.15600921182902155, 0.15503529766260601, 0.155729413283974, 0.15272695966596359, 0.1511611639619474, 0.15243101889716326, 0.1504670483405794, 0.14976217123670957, 0.14602255606732384, 0.14609250570358573, 0.1435258921268994, 0.14456975592957685, 0.14302010244931343, 0.14314385949808445, 0.14293401058929878, 0.14023287848133958, 0.13657935517196848, 0.13867784425982524, 0.13564310619430933, 0.13521802763548707, 0.13430330162283155, 0.13203800955619646, 0.1325276570100297, 0.13293659334509922, 0.13242542292626233, 0.1334800482114416, 0.13343162154018337, 0.1335876630364599, 0.13376522749774009, 0.13200572510869096, 0.13127394429856656, 0.13223709698248032, 0.13090805389350438, 0.13132775171107572, 0.13027850716714734, 0.13046145236967846, 0.13244156515001507, 0.1324899918212733, 0.13217790882872024, 0.13388898454651113, 0.13502970169170506, 0.13565924841806207, 0.13596056992811328, 0.13590138177435324, 0.13598209289311697, 0.13703133743704532, 0.13733265894709656, 0.1390598768886402, 0.1389953079936292, 0.14059338814515088, 0.14327837802935733, 0.14935323490164004, 0.1513172054582239, 0.1526946752184581, 0.15500839395635144, 0.1549976324738496, 0.15669256596788775, 0.16027075889974604, 0.1616858938487366, 0.16512418750807112, 0.16628104687701778, 0.16920817011751538, 0.17047264431148035, 0.17561663294735483, 0.17495480177349232, 0.17949076664801342, 0.18248783952477293, 0.18354784555120313, 0.18789010374069132, 0.18923528905342, 0.18694309328053033, 0.19056433214239593, 0.18904158236838706, 0.1913929662950368, 0.19432547027678532, 0.1961818260083509, 0.19478283328311308, 0.19600964228832163, 0.19524019628944084, 0.1963217252808747, 0.19619258749085275, 0.19685979940596618, 0.1966445697559296, 0.2005240841978391, 0.20309069777452543, 0.2041130386121992, 0.2068841203564203, 0.20886961387800784, 0.21093043777710818, 0.21699453316688908, 0.22100318539882055, 0.2245329516594206, 0.23112974043304205, 0.23603159571262536, 0.23821079591924585, 0.2465670870819164, 0.24615277000559596, 0.2497740088674616, 0.2537557573931385, 0.2575545607162843, 0.26182686926951054, 0.2675681201842366, 0.27064052343850886, 0.27522491498428825, 0.27936270500624166, 0.2843506521458396, 0.2841730876845594, 0.28878438293659336, 0.29272846627351384, 0.29302440704231414, 0.29942748913090267, 0.30610498902328787, 0.31173324437174466, 0.31937927768929447, 0.3272190176918772, 0.3338319486892514, 0.34205910206190004, 0.350517627308338, 0.35876630364599027, 0.3705770306917481, 0.3793584004132409, 0.3891029228186475, 0.3954037708234686, 0.4031681804485386, 0.41228315612758815, 0.42238280745555506, 0.42480952175971765, 0.4287428436141363, 0.43304205587361716, 0.43752421333562913, 0.43765873186690196, 0.4384335586070337, 0.44123154405750936, 0.43899853643837977, 0.4420870819164048, 0.4394020920321984, 0.4369699969867849, 0.43266540398605313, 0.43253626619603114, 0.4367547673367483, 0.4353988205415178, 0.43502754939520466, 0.43859498084456117, 0.4397356979897551, 0.4438035383754466, 0.4484202143687314, 0.45452735568851965, 0.46282983943868106, 0.46660711979682323, 0.47476970427446086, 0.4793271921139856, 0.48419676294606345, 0.489652834574491, 0.49308036675132366, 0.4943986483577978, 0.5025020446816754, 0.5071779088287203, 0.5112134647669063, 0.5182084283930954, 0.5223946450863071, 0.5247890749429641, 0.5262956824932202, 0.5284641212173389, 0.5298846369075804, 0.5335004950281951, 0.5394515948517068, 0.5381978821402437, 0.5420128276871422, 0.5454726443114803, 0.553253196160303, 0.5558574749257458, 0.5644558994447075, 0.5665759114975679, 0.5763903835392363, 0.5802645172398949, 0.5859842451896173, 0.5948140415823684, 0.6078892428220912, 0.6122584047178339, 0.6218845508157204, 0.6271146313116095, 0.6300363738108562, 0.6350512246567087, 0.638381903491025, 0.640146786621325, 0.6470610391287503, 0.6444890448108131, 0.6480403340364168, 0.6502679609142955, 0.653770823468641, 0.6541689983212088, 0.6531574189660367, 0.6515970040032715, 0.653265033791055, 0.6467920020662047, 0.6506930394731179, 0.6444567603633077, 0.641847100856614, 0.6417125823253411, 0.6390006887348801, 0.6382097197709956, 0.6386025138823124, 0.6374564159958676, 0.6407494296414274, 0.64500021522965, 0.6529260470922474, 0.6575965304980415, 0.6602976626060006, 0.6662810468770177, 0.6668137402608584, 0.673082303818174, 0.6724635185743189, 0.6782370539365503, 0.679582239249279, 0.6784361413628341, 0.6851190219964702, 0.6871045155180577, 0.6847369893676553, 0.6890200594033834, 0.6904674787998795, 0.6877663466919203, 0.6915597692738151, 0.6915382463088116, 0.6885896001033103, 0.685135164220223, 0.6837038870474796, 0.6791517799492058, 0.681723774267143, 0.6809543282682622, 0.6814547372045973, 0.6839836855925272, 0.6904836210236323, 0.688541173432052, 0.6939649606129741, 0.6995501700314235, 0.699517885583918, 0.7040161852696828, 0.7121895312298222, 0.7178231673195299, 0.7216596358314321, 0.7233169041367139, 0.7322489346132324, 0.7306400929792088, 0.7369086565365245, 0.7331313761783823, 0.7339331066247686, 0.7352782919374973, 0.7378556669966855, 0.7386358744780681, 0.7418051310748569, 0.7440811846239938, 0.7438982394214627, 0.7354074297275193, 0.738452929275537, 0.7312158322930566, 0.7262225044122078, 0.7225044122078258, 0.7184042873746287, 0.715923765657957, 0.7145140114502174, 0.709929619904438, 0.711635314880978, 0.7121249623348113, 0.7091171279755499, 0.7108389651758427, 0.7123617149498515, 0.7076320433902974, 0.7148045714777668, 0.7159721923292153, 0.7182536266196031, 0.7199485601136413, 0.7227250225991133, 0.7192867289397787, 0.7220847143902545, 0.7184634755283887, 0.7208848090913004, 0.724947268735741, 0.7238226938142999, 0.7219017691877233, 0.7238011708492962, 0.7248181309457191, 0.7205081572037364, 0.7184742370108906, 0.7174196117257113, 0.7167846842581034, 0.7175272265507296, 0.7089664672205243, 0.7080840256553743, 0.7117052645172399, 0.7061523395462959, 0.7084606775429383, 0.7080086952778615, 0.7069594507339331, 0.7096498213593905, 0.7091225087168008, 0.707212345572726, 0.7105537858895441, 0.7205458223924928, 0.7201637897636779, 0.7279228186474969, 0.7339384873660195, 0.7346541259523912, 0.7403308079721063, 0.7452918514054496, 0.7426768111575051, 0.7449205802591365, 0.7497578666437088, 0.7507210193276226, 0.7513936119839869, 0.755111704188369, 0.7462281003831088, 0.7522437691016315, 0.7484019198484784, 0.7461742929705997, 0.7385228789117989, 0.7325771598295381, 0.7230855322629245, 0.7157246782316732, 0.7126307520123972, 0.7043013645559812, 0.6998030648702165, 0.6944061813955491, 0.684946838276441, 0.6871367999655632, 0.6857539494640782, 0.6835747492574578, 0.6810511816107787, 0.6871906073780724, 0.6814009297920881, 0.682670784727304, 0.6837200292712324, 0.6835478455512032, 0.6828106839998278, 0.6864964917567044, 0.6842903878438293, 0.6868462399380139, 0.693900391717963, 0.686770909560501, 0.6891384357109035, 0.6907688003099307, 0.6901769187723301, 0.6870722310705523, 0.6875295940768801, 0.6819820498471869, 0.6844302871163531, 0.6865341569454608, 0.6817560587146485, 0.6821488528259653, 0.6795660970255263, 0.6785760406353579, 0.6803409237656579, 0.6818260083509105, 0.679829753346821, 0.6850490723602083, 0.6894128535147002, 0.6906934699324179, 0.692259265636434, 0.7078418922990831, 0.708358443459171, 0.7116245533984762, 0.7231016744866773, 0.7248019887219663, 0.7260180362446731, 0.7360423571951272, 0.7338039688347466, 0.7381892729542422, 0.7425638155912359, 0.7402554775945934, 0.7417836081098532, 0.7445008824415652, 0.7420311222073953, 0.7423109207524429, 0.7406375102234084, 0.7348263096724205, 0.7267444363135466, 0.723559037493005, 0.713082734277474, 0.7132979639275107, 0.702423485859412, 0.6958482200507942, 0.6930502346003186, 0.688923206060867, 0.6802440704231415, 0.6847100856614007, 0.6828967758598424, 0.6803947311781671, 0.6818582927984159, 0.689106151263398, 0.6867224828892429, 0.6889447290258706, 0.6940349102492359, 0.690376006198614, 0.694158667298007, 0.6966284275321768, 0.6944546080668073, 0.694664456975593, 0.7009330205329086, 0.6959504541345616, 0.69876458180879, 0.7061146743575395, 0.6963432482458783, 0.700152813051526, 0.7046511127372906, 0.6999106796952348, 0.700717790882872, 0.7022243984331281, 0.6990067151650812, 0.696929749042228, 0.7007285523653739, 0.6966929964271878, 0.6975754379923378, 0.7051192372261202, 0.7068733588739184, 0.7072769144677371, 0.7098865739744307, 0.7138575610176058, 0.7198570875123758, 0.7307477078042272, 0.7313503508243295, 0.7404545650208773, 0.7493489303086394, 0.748547199862253, 0.7552462227196418, 0.7666264904653265, 0.7604440187680255, 0.76994102707589, 0.7763548706469803, 0.7734815548189918, 0.7787869656923938, 0.7881655976927382, 0.7801482932288752, 0.7864491412336964, 0.7861155352761396, 0.7836081098532134, 0.7753217683268047, 0.7757629891093797, 0.7658785674314493, 0.763381903491025, 0.7587329430502346, 0.7515550342215144, 0.7481328827859326, 0.754605914510783, 0.7421172140674099, 0.7456738840342646, 0.7513021393827214, 0.7531692565967888, 0.7525773750591882, 0.7597122379579011, 0.7564837932073523, 0.7651575481038267, 0.7698441737333735, 0.7671538031079161, 0.7761073565494383, 0.7780390426585166, 0.7750473505230081, 0.7745038956566657, 0.7821445482329645, 0.7745738452929275, 0.7750688734880117, 0.7835004950281951, 0.7745630838104257, 0.7742671430416254, 0.7727551547501184, 0.7666318712065774, 0.7642751065386768, 0.7660776548577332, 0.7537342344281348, 0.7532553484568034, 0.7510546252851793, 0.7500107614825018, 0.745582411432999, 0.7505811200550988, 0.7420956911024063, 0.745388704747966, 0.7490906547285954, 0.7450820024966639, 0.7469975463819896, 0.7482135939046963, 0.7511245749214411, 0.7632742886660067, 0.7656525762989109, 0.7628384486246825, 0.7645064784124661, 0.7726098747363437, 0.7707373767810254, 0.7726206362188455, 0.7804496147389265, 0.7705490508372433, 0.7759513150531617, 0.7822037363867247, 0.7780605656235203, 0.7785340708536008, 0.7766185269682752, 0.7701993026559338, 0.7725453058413327, 0.7730510955189187, 0.758899746029013, 0.7572048125349748, 0.754611295252034, 0.744382506134045, 0.7405406568808919, 0.7456523610692609, 0.7347725022599113, 0.7377157677241617, 0.739023287848134, 0.7372745469415867, 0.7392438982394215, 0.7460559166630795, 0.7409334509922086, 0.7423539666824501, 0.7457599758942792, 0.7444040290990487, 0.7476755197796049, 0.7486332917222677, 0.7436614868064224, 0.7442210838965175, 0.7426929533812578, 0.7405406568808919, 0.7444632172528087, 0.7467554130256984, 0.7418105118161078, 0.7467016056131893, 0.7429888941500581, 0.7415522362360639, 0.7407343635659248, 0.7418481770048642, 0.7349124015324351, 0.7351545348887263, 0.7427682837587706, 0.7394376049244544, 0.7389102922818648, 0.746405664844389, 0.7348424518961733, 0.7505326933838405, 0.7470943997245061, 0.7455662692092463, 0.7547242908183032, 0.7634464723860359, 0.7557143472084714, 0.7657978563126856, 0.7683967543368775, 0.7680201024493134, 0.7734923163014937, 0.7792012827687143, 0.7734600318539882, 0.7772642159183849, 0.7854375618785244, 0.7802612887951444, 0.784076234342043, 0.7896722052429943, 0.7802666695363953, 0.7850393870259567, 0.785082432955964, 0.7777538633722182, 0.7758167965218888, 0.7765539580732642, 0.7671645645904179, 0.7679447720718006, 0.7692738151607765, 0.7593033016228316, 0.7582701993026559, 0.7629944901209591, 0.753282252163058, 0.7572801429124876, 0.7654965348026345, 0.761864534458267, 0.7649476991950411, 0.7694459988808058, 0.7656687185226637, 0.7716790064999355, 0.7747245060479532, 0.7697419396496061, 0.7735730274202575, 0.7800837243338642, 0.7704791012009814, 0.7763925358357368, 0.7758598424518962, 0.7733201325814644, 0.7738582067065559, 0.7776570100297017, 0.7747567904954586, 0.7775870603934398, 0.7793680857474926, 0.7720395161637467, 0.7758275580043907, 0.7748052171667169, 0.7682191898755972, 0.7725022599113254, 0.7738958718953123, 0.7689724936507253, 0.7713615427661314, 0.773094141448926, 0.7695912788945806, 0.7747460290129569, 0.7801967199001334, 0.7783026989798114, 0.7817248504153932, 0.7918460247083638, 0.7825965304980415, 0.7914155654082906, 0.7959461495415608, 0.7931427833498342, 0.7936378115449184, 0.8029626361327536, 0.798840988334553, 0.7938907063837114, 0.7983567216219707, 0.7986634238732728, 0.7956394472902587, 0.8006435366536094, 0.7955533554302441, 0.7949722353751453, 0.7917814558133528, 0.7893547415091903, 0.7858249752485903, 0.7823328741767466, 0.778964530153674, 0.7764571047307477, 0.7734385088889846, 0.7684720847143902, 0.7633227153372648, 0.7714906805561534, 0.7649584606775429, 0.7699195041108863, 0.76994102707589, 0.7700055959709009, 0.7775978218759416, 0.7833175498256639, 0.7772157892471266, 0.7789860531186776, 0.7835435409582024, 0.7746330334466877, 0.7738582067065559, 0.7746276527054367, 0.7648077999225174, 0.7681707632043391, 0.7692791959020274, 0.7622573285695837, 0.7623757048771038, 0.7664865911928027, 0.759152640867806, 0.7571241014162111, 0.757441565150015, 0.7522599113253842, 0.7513936119839869, 0.7534382936593345, 0.7435108260513968, 0.7424831044724721, 0.7407128406009211, 0.7349285437561879, 0.7398088760707675, 0.7392546597219233, 0.7358809349575998, 0.7392600404631742, 0.7386358744780681, 0.7384152640867806, 0.7394214627007016, 0.7389479574706211, 0.7351922000774826, 0.7363921053764366, 0.7402393353708406, 0.7346218415048857, 0.7310328870905256, 0.7297146054840515, 0.7246459472256898, 0.7240540656880892, 0.722800352976626, 0.7201906934699324, 0.7169245835306272, 0.7183666221858723, 0.7137069002625802, 0.7117967371185054, 0.7100103310232018, 0.7009222590504068, 0.6993026559338814, 0.6969727949722354, 0.683929878180018, 0.6868570014205156, 0.6834994188799449, 0.6707632043390297, 0.6688691834187077, 0.6832734277474065, 0.6752184580947871, 0.6740669794670914, 0.6724742800568206, 0.6706932547027679, 0.6677553699797684, 0.6718501140717145, 0.6650004304593, 0.6662595239120142, 0.662982652490207, 0.6579624209031036, 0.6565580474366148, 0.6540560027549395, 0.646641341311179, 0.6488366837415522, 0.6428048727992768, 0.6356915328655676, 0.638441091644785, 0.6338136541689984, 0.6305152597821876, 0.6270016357453403, 0.6254573630063277, 0.6175853385562395, 0.6167728466273514, 0.6169665533123844, 0.6076094442770436, 0.6100253970987043, 0.6053387714691576, 0.602788300116224, 0.6004638198958289, 0.6015776333347682, 0.5995921398131807, 0.6035954113038612, 0.6056400929792088, 0.5956372949937584, 0.6004853428608325, 0.6027452541862167, 0.5987796478842925, 0.5968748654814687, 0.5975044122078258, 0.5942113985622659, 0.5949808445611467, 0.5958794283500495, 0.5907031552666695, 0.5949324178898885, 0.594900133442383, 0.5899337092677888, 0.5960892772588352, 0.596734966208945, 0.5929146399207955, 0.6012494081184624, 0.5982254315354483, 0.5984083767379794, 0.5980424863329172, 0.601405449614739, 0.5979671559554044, 0.6049190736515863, 0.6084111747234299, 0.6114997202014549, 0.6141685678619087, 0.6236601954285222, 0.623229736128449, 0.6293691618957428, 0.6410507511514786, 0.6438056906719469, 0.6478251043863803, 0.6639135207266152, 0.6620679264775515, 0.6727809823081228, 0.6793401058929878, 0.6781993887477938, 0.6858454220653437, 0.6894074727734493, 0.6930179501528131, 0.6992111833326159, 0.7051838061211313, 0.7057810683999828, 0.7067388403426456, 0.7164995049718049, 0.7194427704360553, 0.7226819766691059, 0.7291012009814472, 0.7265668718522663, 0.7376673410529034, 0.7382161766604968, 0.7369517024665317, 0.7444147905815505, 0.7466047522706728, 0.7477616116396195, 0.7512537127114631, 0.7606538676768112, 0.7640491154061384, 0.7678479187292842, 0.7771942662821231, 0.7768821832895699, 0.7865244716112092, 0.7886713873703242, 0.7930351685248159, 0.8024084197839094, 0.8064385949808446, 0.8063901683095863, 0.8067668201971504, 0.8181148034953295, 0.8171408893289139, 0.8244533166889071, 0.8274127243769102, 0.825464896044079, 0.8285480607808532, 0.8386638543325728, 0.8387660884163403, 0.8411013301192373, 0.8458094787137876, 0.8439638844647238, 0.8474021781240584, 0.8504046317420688, 0.8458040979725368, 0.8464336446988937, 0.8518843355860704, 0.8479402522491498, 0.8515076836985063, 0.857082131634454, 0.8567700486419009, 0.863829581163101, 0.8771522965003659, 0.8779325039817485, 0.88452929275537, 0.8914704489690499, 0.8916533941715811, 0.8965283457449098, 0.8994931341741639, 0.8974323102750635, 0.9000204468167535, 0.910211570745986, 0.9023395462958977, 0.9151618526968275, 0.9103837544660153, 0.915317894193104, 0.9187615685936895, 0.9218985407429727, 0.9201228961301708, 0.9190252249149843, 0.9199883775988981, 0.923771038698291, 0.922953166028152, 0.9245835306271792, 0.9176800396022556, 0.9235073823769963, 0.9213604666178813, 0.922076105204253, 0.9245996728509319, 0.9254552107098274, 0.921946967414231, 0.927182428651371, 0.9295338125780207, 0.9293508673754897, 0.9344571908226077, 0.9411077870087383, 0.9381914252507425, 0.9453478111144591, 0.9512773879729671, 0.9461710645258491, 0.9585360079204511, 0.9635347165425509, 0.9603869829107657, 0.9655847789591494, 0.9709332357625586, 0.9676456028582497, 0.9752485902457922, 0.9805701433429469, 0.9750333605957556, 0.9816086264043735, 0.9860423571951272, 0.9801504455253756, 0.9744630020231587, 0.97476432353321, 0.9698086608411175, 0.9686625629546727, 0.9679953510395592, 0.9642503551289225, 0.9650574663165598, 0.9681083466058285, 0.9637069002625802, 0.9642449743876716, 0.9687917007446946, 0.9705404416512419, 0.9720147647539925, 0.9791872928414619, 0.9766099177822737, 0.9767013903835392, 0.9826686324308037, 0.9782564246050536, 0.9808068959579872, 0.9851114889587189, 0.9827654857733201, 0.9874574921441178, 0.9932848349188584, 0.9856172786363049, 0.991358529551031, 0.9942103224140157, 0.9914500021522965, 0.9957868796005338, 1.0, 0.9900241057208041, 0.9888780078343593, 0.9873875425078559, 0.981199690069304, 0.9757436184408764, 0.9800966381128664, 0.9729617752141535, 0.972364512935302, 0.9710300891050752, 0.9653910722741165, 0.969206017821015, 0.9617267874822436, 0.960435409582024, 0.9593538805905901, 0.9644978692264646, 0.960247083638242, 0.9717887736214541, 0.9738980241918127, 0.9680276354870647, 0.9717511084326977, 0.9767336748310447, 0.9736074641642632, 0.9702714045886961, 0.976405449614739, 0.9738334552968017, 0.9739518316043219, 0.9785954113038612, 0.972547458137833, 0.9723806551590547, 0.9728057337178769, 0.9709547587275623, 0.9640028410313805, 0.9640136025138824, 0.9564913262451036, 0.9527032844044595, 0.9506047953166028, 0.9475162498385777, 0.9425874908527399, 0.942582110111489, 0.9393160001721838, 0.939870216521028, 0.9416028152038225, 0.9404674787998795, 0.9376856355731565, 0.9482964573199604, 0.9460742111833326, 0.9412584477637639, 0.9462786793508674, 0.9423991649089578, 0.9381968059919935, 0.9399509276397917, 0.9382183289569971, 0.9374381214756144, 0.939106151263398, 0.9357808531703328, 0.93582389910034, 0.9406450432611596, 0.9331711936636391, 0.9319874305884379, 0.9303785889544144, 0.9247072876759502, 0.9212582325341139, 0.9245996728509319, 0.9160281520382247, 0.9177553699797684, 0.9200314235289053, 0.9114329990099436, 0.9074189660367612, 0.912100210925057, 0.906800180792906, 0.9063374370453273, 0.9055733717876975, 0.9030390426585166, 0.903329602686066, 0.9010105032069218, 0.9034479789935862, 0.9041098101674486, 0.9036309241961172, 0.9064988592828548, 0.9044111316774999, 0.9085004950281951, 0.9062943911153201, 0.9083390727906676, 0.9104752270672808, 0.9110886315698851, 0.9091677069433085, 0.9200206620464035, 0.9136229606990659, 0.9167868365546038, 0.921129094744092, 0.9125252894838793, 0.9160981016744867, 0.9245996728509319, 0.9077525719943179, 0.9103622315010116, 0.9106420300460591, 0.9101739055572295, 0.9066279970728768, 0.9093936980758469, 0.9098456803409237, 0.9100609099909603, 0.9111908656536525, 0.91235310576385, 0.9123154405750936, 0.9179598381473032, 0.9126598080151521, 0.9107980715423357, 0.9139242822091171, 0.9071606904567173, 0.9072898282467393, 0.90805927424562, 0.9023718307434032, 0.9035017864060954, 0.9076395764280487, 0.9022050277646249, 0.9004240024105721, 0.9030121389522621, 0.9012903017519693, 0.8978466273513839, 0.9032542723085533, 0.897276268778787, 0.9030498041410184, 0.9028560974559855, 0.901252636563213, 0.9009835995006672, 0.9019951788558391, 0.8959203219835564, 0.8916211097240756, 0.8973139339675433, 0.8975775902888382, 0.8983900822177263, 0.9027753863372218, 0.9022480736946322, 0.8987990185527959, 0.8997783134604623, 0.8970879428350049, 0.9009351728294089, 0.9004724290818303, 0.8983685592527226, 0.8987613533640394, 0.9009351728294089, 0.8961247901510913, 0.9002625801730446, 0.9077740949593216, 0.9005100942705867, 0.8994500882441565, 0.9054011880676682, 0.9018714218070681, 0.90017648831303, 0.9053366191726572, 0.9043196590762344, 0.9017369032757953, 0.9061275881365417, 0.8970718006112522, 0.8979380999526495, 0.9029421893160001, 0.8948334122508717, 0.8992725237828764, 0.9039483879299213, 0.9012849210107184, 0.9071068830442082, 0.9074082045542594, 0.9113200034436744, 0.9058316473677414, 0.9117773664500022, 0.904976109508846, 0.9044918427962636, 0.9072683052817356, 0.9011073565494383, 0.8961247901510913, 0.8986967844690285, 0.893429038784383, 0.8931600017218372, 0.8908301407601911, 0.8829043088975937, 0.8864233136756919, 0.889199776161164, 0.8840127415952822, 0.8826245103525462, 0.8814730317248504, 0.8807251086909733, 0.8783844862468254, 0.8745695406999269, 0.8739615169385735, 0.8737193835822823, 0.8763559467952305, 0.8714540915156472, 0.8675530541087341, 0.8700550987904093, 0.8748547199862253, 0.8682095045413456, 0.8728477034996341, 0.8707384529292755, 0.8730844561146743, 0.8714218070681417, 0.8706308381042572, 0.8663961947397874, 0.8661486806422453, 0.8628987129266927, 0.8661432999009944, 0.865976496922216, 0.8673754896474538, 0.8620270328440446, 0.8593850888898454, 0.8597455985536567, 0.8576955361370583, 0.86111768757264, 0.8575287331582799, 0.8567861908656537, 0.8573995953682579, 0.8544993758340149, 0.8529281993887478, 0.8513839266497353, 0.8473376092290474, 0.8464444061813956, 0.8425810339632388, 0.8412250871680083, 0.8402780767078473, 0.8364362274546941, 0.8401973655890835, 0.8410744264129827, 0.8446849037923464, 0.8413488442167794, 0.8379482157462012, 0.8403534070853601, 0.8343538805905901, 0.8285426800396023, 0.8328795574878395, 0.8223655890835522, 0.81852912057165, 0.8167104300288408, 0.8147841246610134, 0.8103988205415178, 0.8080366751323662, 0.7992337824458697, 0.8026344109164478, 0.8002130773535362, 0.7904039860531187, 0.7885852955103095, 0.7897636778442598, 0.7875199087426283, 0.7838771469157592, 0.7849909603546985, 0.7785017864060954, 0.7817625156041497, 0.7842807025095777, 0.7782435108260514, 0.779658645775042, 0.7779852352460075, 0.7728573888338858, 0.774611510481684, 0.774988162369248, 0.7677026387155095, 0.7655826266626491, 0.7630213938272137, 0.7591580216090569, 0.761046661788128, 0.7558058198097369, 0.7508393956351427, 0.7507802074813826, 0.7504035555938185, 0.7451411906504241, 0.7479553183246525, 0.7423324437174466, 0.7372584047178339, 0.7395075545607163, 0.7348962593086824, 0.7316301493693771, 0.7311082174680384, 0.7298867892040808, 0.7218910077052215, 0.7232146700529465, 0.7169461064956308, 0.7157569626791787, 0.7104784555120314, 0.7052806594636477, 0.7073199603977444, 0.7039354741509191] 27 | TAXI_SPEED_DISTRIBUTION = [6.524102629210642, 6.546463526945012, 6.567956705367584, 6.588598903461066, 6.608406860208169, 6.627397314591603, 6.645587005594075, 6.662992672198297, 6.679631053386977, 6.695518888142825, 6.7106729154485505, 6.725109874286861, 6.738846503640469, 6.751899542492082, 6.76428572982441, 6.776021804620163, 6.787124505862049, 6.797610572532777, 6.807496743615059, 6.816799758091603, 6.825536354945118, 6.833723273158315, 6.841377251713901, 6.848515029594587, 6.855153345783083, 6.861308939262097, 6.8669985490143395, 6.872238914022519, 6.877046773269345, 6.881438865737529, 6.885431930409777, 6.8890427062688016, 6.89228793229731, 6.895184347478013, 6.897748690793619, 6.899997701226838, 6.90194811776038, 6.903616679376953, 6.905020125059267, 6.906175193790033, 6.907098624551958, 6.907807156327753, 6.9083175281001274, 6.908646478851789, 6.908810747565449, 6.908827073223817, 6.908712194809601, 6.908482851305511, 6.908155781694256, 6.907747724958547, 6.907275420081092, 6.906755606044601, 6.9062050218317825, 6.905640406425348, 6.905078498808004, 6.904536037962463, 6.904029762871432, 6.903576412517622, 6.903192725883742, 6.902895441952501, 6.902701299704348, 6.906754768594559, 6.921966696801165, 6.940822456870921, 6.95263767034037, 6.953010727998542, 6.965824591489537, 6.9715608612509055, 6.9602133996610025, 6.985033953736527, 6.988722991763325, 6.992676161312925, 7.014573654255106, 7.0274598892365265, 7.033261897654854, 7.034390138948801, 7.053793978683457, 7.04693821635852, 7.039699507413591, 7.044834173527815, 7.048775034660343, 7.052733340158507, 7.043414592231259, 7.056092337471228, 7.063643999064063, 7.061799985510275, 7.08122977104021, 7.096520994178246, 7.114321949199943, 7.1274009982281195, 7.148798016943959, 7.169600433825675, 7.184033164976006, 7.1832959139471, 7.202088307842684, 7.1991734331941855, 7.182330389884138, 7.183327595892526, 7.197344132321815, 7.224252184352763, 7.240730520968947, 7.246840640265434, 7.236845851135065, 7.243757926624717, 7.249424495182015, 7.266872453503202, 7.263209010956104, 7.275384674530802, 7.272264523057485, 7.255251456793475, 7.267994887486499, 7.272460540431561, 7.261881435369434, 7.261510182829127, 7.24611825976673, 7.232495539637871, 7.228507026583065, 7.2217358831304965, 7.2073504973556055, 7.246539701098323, 7.245512356799288, 7.253431776752833, 7.2706131996082926, 7.2577628315148965, 7.269625862688431, 7.282016108823393, 7.268666587923187, 7.270531250394879, 7.2584500939389205, 7.259467231812541, 7.2788423786154075, 7.259639216693311, 7.251404053093822, 7.274451173554011, 7.276381464567414, 7.269006698580725, 7.260471852845345, 7.236977486235393, 7.2198417089706775, 7.2035287014154425, 7.216923828933154, 7.187292328457201, 7.173184108050412, 7.178846551532479, 7.166595887875709, 7.136352124420731, 7.131468699883739, 7.132898957261751, 7.1615886544191625, 7.1418427245945875, 7.140223837709636, 7.141516190667834, 7.162072569878241, 7.189246665874084, 7.190663273242901, 7.190892930208257, 7.158100256037898, 7.143842459070865, 7.114427247717186, 7.113299870011195, 7.11184778471377, 7.0998073493363565, 7.083767122287067, 7.0674079627815445, 7.067312118409815, 7.083748330770252, 7.090406350557365, 7.072399917954618, 7.047410627123433, 7.045438499367952, 7.044584501380668, 7.030250591576786, 7.015339394546216, 7.025649686942931, 7.003853176282017, 7.007924361769409, 7.029961895484334, 7.030543522466083, 7.02300112202421, 7.01504267586673, 7.016592370675228, 7.049599465349111, 7.0375124925195545, 7.036740021833623, 7.049822722076894, 7.052378957011858, 7.053502970206422, 7.058398636941843, 7.06328677913391, 7.075626340060527, 7.066743076950424, 7.068767112266865, 7.092716951375364, 7.064910112955846, 7.072578742076242, 7.088753786570413, 7.096467396649197, 7.090469484708122, 7.09430825158062, 7.100742189954183, 7.098550821568355, 7.119913038745491, 7.112075020558897, 7.13767178962133, 7.1268820868267655, 7.112303187823884, 7.11066669886113, 7.124486607501596, 7.136977020985357, 7.116918219804155, 7.121134272002295, 7.0903591335317495, 7.106475422299277, 7.087590041634059, 7.113060087263392, 7.101432737941812, 7.111588052631307, 7.132284328916556, 7.122829859441274, 7.0896323797581005, 7.094431695509833, 7.076601321075422, 7.06156760090906, 7.105357836763098, 7.132211929763713, 7.149024036793291, 7.114805641497647, 7.148052090268684, 7.132780344355716, 7.166425951679274, 7.170409194848647, 7.157249331484322, 7.196426905891478, 7.188985699815198, 7.210082026766931, 7.208962653273003, 7.221561265346945, 7.226311251002797, 7.232935635100388, 7.276163917990072, 7.293955605512372, 7.303031851389701, 7.332728380405331, 7.353545867548627, 7.426349418566988, 7.430480859404816, 7.424576376682491, 7.4788178409462045, 7.51974031658977, 7.530293881181777, 7.601026557354277, 7.594042976345452, 7.649621370416259, 7.667413881128461, 7.71760631837079, 7.723589451271976, 7.7415149243982, 7.7635048880336734, 7.825171320371885, 7.917193935399905, 7.9674114154767395, 7.980618520798833, 8.019900835990265, 8.061649786213106, 8.083599642600896, 8.117373871791207, 8.167349208058281, 8.206476835976238, 8.284772369496524, 8.324926467881909, 8.401522971648662, 8.435469519335417, 8.471436843406195, 8.487375369952318, 8.555359213279253, 8.591980101948234, 8.610920984531937, 8.716672573102334, 8.766483560091816, 8.81687534578692, 8.907738068035439, 8.962475843138638, 8.987189770241455, 9.023345866562451, 9.099020241995685, 9.123922328445177, 9.162748522292286, 9.186756508417457, 9.221553395175029, 9.294792944754736, 9.331191455964003, 9.368978918859431, 9.405693418172081, 9.44423554717214, 9.472257387726993, 9.525427666934956, 9.574262175929377, 9.605661591700805, 9.624016739384366, 9.698780824344636, 9.724161148670298, 9.790445248691324, 9.837810066090436, 9.899872086537647, 9.934860776508835, 9.950526508763922, 9.96283984782749, 10.028394734206639, 10.035778419136637, 10.042225157695109, 10.049229199222863, 10.072248235246787, 10.087905314560025, 10.098929445511855, 10.165573593113107, 10.16428749669205, 10.187295913294859, 10.18127214750355, 10.187939295467284, 10.190363604092898, 10.225786959957736, 10.23898839684163, 10.222323153674967, 10.25023506940813, 10.241551083948472, 10.256227856121368, 10.26416384808002, 10.272539396670458, 10.248944001365905, 10.22344388240544, 10.242565788397123, 10.258035282361382, 10.273866935102985, 10.253537283059917, 10.256103637830387, 10.222049454176169, 10.227862298226114, 10.20438525596818, 10.190899748612264, 10.206547364577274, 10.203372510764481, 10.160589296789178, 10.15295171423733, 10.149474704721039, 10.105957793074829, 10.057408400738444, 10.043615336493366, 10.052250844588889, 10.007016776404347, 9.990289412762216, 9.93091554145118, 9.923296824320824, 9.916270264017681, 9.85627663206155, 9.854198220904138, 9.792763463772502, 9.772452200939066, 9.75518655276341, 9.746552368906503, 9.745444915666727, 9.697195079265633, 9.678130054800665, 9.664761198248495, 9.619620384693699, 9.5887305468941, 9.522694511326003, 9.502427349146888, 9.490559001707936, 9.444262984826127, 9.397064364422988, 9.378262174879694, 9.308981136604714, 9.299677728951297, 9.250286227929777, 9.235134235840336, 9.167436111445468, 9.150007863932837, 9.127983763831988, 9.11196919329511, 9.062928830418846, 8.982789522686126, 8.939537153485443, 8.914876111851322, 8.89525901084043, 8.877877029266026, 8.856384194754138, 8.836521172918742, 8.817918037920043, 8.80174903957953, 8.738694203124762, 8.696888121317103, 8.659075379929789, 8.638566363903271, 8.619231921965582, 8.613482055405777, 8.535817653209191, 8.51771379927314, 8.4984625828491, 8.425306594794131, 8.380653616027583, 8.365069419644483, 8.29689347700019, 8.248181533690733, 8.224022964510707, 8.204652653762457, 8.14482254976378, 8.123637810910799, 8.074875041850454, 8.049293204000994, 8.016132103276966, 7.969814267497799, 7.949511128747844, 7.933281610115345, 7.912732449877741, 7.8928587443228775, 7.863017865277121, 7.846918576519689, 7.810226511038482, 7.79429835245363, 7.7828019254870675, 7.719847177684903, 7.715449906380945, 7.654649649544852, 7.604316826400912, 7.569718133165127, 7.535388602117159, 7.5020686497072155, 7.484014227861058, 7.423887828971147, 7.406966639815333, 7.388471952489004, 7.370456710431932, 7.335226508862654, 7.323190362372748, 7.287820671283389, 7.228940793722626, 7.208820566124803, 7.161402996882785, 7.14282271054442, 7.109405565417123, 7.091224544330768, 7.041934351057717, 6.985849984605741, 6.963341934929471, 6.9321207718176066, 6.8979307911560825, 6.857802153999157, 6.836733059118783, 6.81862272821006, 6.7984785441351026, 6.776791223575811, 6.742794029999158, 6.691126425018005, 6.655795126952583, 6.6209280396631724, 6.597641587693168, 6.578267956789991, 6.508445217100239, 6.489332999610096, 6.378978612968199, 6.341481480654713, 6.328125616773525, 6.312314963936392, 6.305312900392162, 6.292089500193566, 6.278990721935312, 6.265969628675973, 6.23036187595125, 6.194226832375522, 6.167675541034982, 6.130296740152622, 6.0929087107018765, 6.073436191967268, 6.053478097655983, 6.017385986905519, 5.991230905426975, 5.99445749091677, 5.987217633475525, 5.979472716423188, 5.961073158950412, 5.935727756012134, 5.909378248587099, 5.898705167724413, 5.885218802487418, 5.877904032394692, 5.855475822879204, 5.843804630443299, 5.835468693868957, 5.82176024010091, 5.810259763168309, 5.799892934670038, 5.789664902968423, 5.780194767367885, 5.760335217597921, 5.728541429684938, 5.718925834759212, 5.698359675393353, 5.687532342338552, 5.679998256475347, 5.643113924545995, 5.637479902585472, 5.637107230816231, 5.62140668537544, 5.615992453182365, 5.602864307285946, 5.568125557203189, 5.564409856956456, 5.558203970516072, 5.552447618473336, 5.525162322593501, 5.500823324685458, 5.496966713731666, 5.486049507354313, 5.485379275956736, 5.4868250231106614, 5.450855358073161, 5.453589834512987, 5.430130487611895, 5.433197713006939, 5.412828308837604, 5.415904192184169, 5.414952127022654, 5.41477869610508, 5.417859619489303, 5.421491893265097, 5.421485693229431, 5.422966068318933, 5.421951005180615, 5.42182274656327, 5.423658432587867, 5.435382333867564, 5.441978020533062, 5.440310027474992, 5.431872030608739, 5.430043063258652, 5.4282853983807655, 5.437636942448192, 5.439922438556595, 5.438990841576464, 5.437953893888234, 5.4387414520185535, 5.437504903289366, 5.403918912437288, 5.402635744014242, 5.417856701354548, 5.417323097321402, 5.418820973303236, 5.413770450482153, 5.393071118195349, 5.39242476022316, 5.391508012742897, 5.371846840000928, 5.3779308709360825, 5.378384367802253, 5.377561545029612, 5.384407275823764, 5.383537953723239, 5.382027845263874, 5.383398637857443, 5.377497509345092, 5.375786029124986, 5.375778377255646, 5.3645483554948985, 5.381796968259384, 5.382359225668292, 5.378905485873589, 5.3753459610473815, 5.363496045422183, 5.349332590030577, 5.342162808103531, 5.341730433742801, 5.336118640133189, 5.33001438750995, 5.323254673536316, 5.316815704677172, 5.312450120561728, 5.312222746383113, 5.268044161222101, 5.28462324064519, 5.277749358335136, 5.357530968715063, 5.3623148864295125, 5.363278021571637, 5.366489991186998, 5.361819053133418, 5.363838434399968, 5.360101454987035, 5.362800094470903, 5.3068889110652835, 5.310431162314984, 5.3111970598813345, 5.335335794337928, 5.341842800838224, 5.349340904889914, 5.353385479063904, 5.38982541275614, 5.3986638468256904, 5.401628341686926, 5.404822358233569, 5.404508233448988, 5.413887279142723, 5.420106239691729, 5.423123142129726, 5.423326649404617, 5.42620138997018, 5.423371830088628, 5.4207674180379675, 5.417923693117639, 5.416310186975878, 5.412019869198803, 5.409382483601802, 5.406447557605887, 5.403764489142878, 5.396048477875473, 5.37137186683534, 5.396672698017669, 5.389952502587261, 5.390647188777804, 5.390293248917437, 5.386396482114524, 5.401527490637695, 5.397387122802319, 5.368898683693738, 5.3635993319627575, 5.3585006084950635, 5.355532183049048, 5.330477862429967, 5.337541734454358, 5.34210345841763, 5.345822749252097, 5.369558413946817, 5.395181073176801, 5.403835986425922, 5.407108006194359, 5.389299746737851, 5.394846475816686, 5.426274555383013, 5.430792112320298, 5.467008258974575, 5.45076635737116, 5.483576729007375, 5.491808165704909, 5.5060818391223885, 5.514839899894996, 5.521730751009061, 5.526939938665187, 5.475543511122221, 5.481294858129347, 5.492466641713739, 5.503190315636026, 5.510108058082619, 5.5138143323268745, 5.459523766678179, 5.4659800619917105, 5.471654192320253, 5.479140038529216, 5.485907033134152, 5.490462958363834, 5.4996327506822915, 5.49005550815729, 5.4979588897838045, 5.500993452820873, 5.508968740966168, 5.530442378321138, 5.531633868142032, 5.529923082695581, 5.536001101741413, 5.5325145303408165, 5.481672467520834, 5.507873577368877, 5.513612379502498, 5.493947814897199, 5.523772984899278, 5.524419404342269, 5.534330547278302, 5.486216806508431, 5.486597016089983, 5.495877530774746, 5.512663482921703, 5.521864387555975, 5.534737887609605, 5.538038461521234, 5.537984876172683, 5.519223898425122, 5.522941775763149, 5.501904935578975, 5.500340783192912, 5.506126680913137, 5.4670773686122205, 5.482721430868001, 5.488010221374862, 5.487730090132797, 5.4936020664811, 5.496832378219717, 5.502185267747745, 5.507576514262916, 5.506562263671896, 5.501995570261242, 5.5167426886021715, 5.522766468176894, 5.52543284551333, 5.534608207575222, 5.540405165193376, 5.538410086027768, 5.540148742082122, 5.518310170414127, 5.517875384204491, 5.519698450472551, 5.520250008255558, 5.574407906746083, 5.576847537352704, 5.582988499226984, 5.581932702135035, 5.609865212663551, 5.606085327371872, 5.6010971218302235, 5.56959509632417, 5.569110854839279, 5.57489893351956, 5.572999409088439, 5.550023321467818, 5.545890515936708, 5.540848429342515, 5.534248522442847, 5.535823892224247, 5.529638211411685, 5.528265794528973, 5.503754371877512, 5.495446749415367, 5.49153295583833, 5.4823848922401, 5.482705235494023, 5.475541912278721, 5.474263788370769, 5.466429906797202, 5.482811690396574, 5.452612269872182, 5.453536613475752, 5.446903801095616, 5.437049504775395, 5.431003569672807, 5.429267590577457, 5.421632284834877, 5.435820922118813, 5.431561636192951, 5.428895341890576, 5.421014039054111, 5.4672286553346225, 5.460026398881497, 5.45414665700765, 5.427631204617154, 5.432001116459385, 5.429249198526859, 5.4158180410909225, 5.409702514713331, 5.406551473898475, 5.400861329316892, 5.392021524649006, 5.38757435907733, 5.379956270916173, 5.405445282824541, 5.401770937566111, 5.397663961107337, 5.36556906916288, 5.3547208629760235, 5.346960798405327, 5.338694193049489, 5.311169068624907, 5.313027485684805, 5.30750858434457, 5.294243544695616, 5.289550750931062, 5.293091208186569, 5.346201542186364, 5.345929440518871, 5.350730231926905, 5.350122825877256, 5.351282621088951, 5.352104533386874, 5.3436900900331326, 5.358919052833386, 5.360496475357637, 5.357929249938281, 5.354210282151895, 5.277097178508043, 5.286743721551743, 5.296092791538466, 5.293920421239648, 5.298340245164606, 5.335431342312279, 5.3212537857832825, 5.325600700937104, 5.356035183580272, 5.3649656134231805, 5.382190088628711, 5.386383847371503, 5.446174349383766, 5.457575826622492, 5.462346914608618, 5.464189722299154, 5.444111077565222, 5.446827272003157, 5.430357929038555, 5.415194662914447, 5.4465042871266105, 5.459837744869101, 5.445296473942556, 5.459513969607115, 5.449936628265818, 5.507706245980868, 5.529139766282761, 5.547645845807804, 5.55860374763883, 5.57145599899147, 5.587824838127171, 5.60094082693989, 5.61104331766293, 5.6208446492177355, 5.631822768797131, 5.663158899182598, 5.674736218986805, 5.688498509427096, 5.670434105273533, 5.680531938556235, 5.692313742884453, 5.696379835219358, 5.7308915536322305, 5.731252021640155, 5.736490853778547, 5.712012303270552, 5.720034967074407, 5.726939250086183, 5.7262976565067065, 5.7101997045170085, 5.6976785010108975, 5.695533462714583, 5.706482868170514, 5.738056507066678, 5.741530389088455, 5.7300377249631, 5.728493905099002, 5.757218308024746, 5.732687440830429, 5.736136432837012, 5.740394776381228, 5.733747595021542, 5.736913329900925, 5.712336621271793, 5.7318341133038535, 5.7123167184000545, 5.715945241957286, 5.64604961069417, 5.634429251136368, 5.623349599057777, 5.629501784688938, 5.634330801417428, 5.6359273326679755, 5.669232639497619, 5.649577588339765, 5.656050704583498, 5.661523642226174, 5.661460002329811, 5.664979604059296, 5.675446508954959, 5.674883913206716, 5.677887819505335, 5.673087476592105, 5.680955029165111, 5.680366158784423, 5.653790565642488, 5.630458682425012, 5.650970037568958, 5.622788930026411, 5.622359309711984, 5.626271104406241, 5.628815285687677, 5.650481289481421, 5.652254496015147, 5.65692179596972, 5.654457261959555, 5.644754951662389, 5.628718422103149, 5.618736125633384, 5.607461284812172, 5.618266973912347, 5.6098067003445, 5.6118563152648715, 5.603021399303243, 5.676967047652238, 5.673697579298137, 5.649332552129669, 5.660730582251361, 5.65832595035177, 5.6486900861041045, 5.642082532564195, 5.640545945185132, 5.613725167847887, 5.6107045685057555, 5.607401084038636, 5.605606740831712, 5.605329470744009, 5.597855860354743, 5.5901893990006, 5.5807134634790625, 5.5742032841976075, 5.648971432681763, 5.64361849886198, 5.629537446218189, 5.619509049794763, 5.587886796565456, 5.598765530766753, 5.607962558971196, 5.603639760652791, 5.59663638831405, 5.587395568533326, 5.552892501362371, 5.5478710641183815, 5.546176072572836, 5.5389595639274285, 5.5414850728983, 5.52769844469196, 5.538790538597044, 5.527452913619104, 5.5397950493572035, 5.534989347814335, 5.532199887491236, 5.516794738968952, 5.555458527841361, 5.555275673861922, 5.555869528128092, 5.55791801672973, 5.558098939507495, 5.556882024175247, 5.5611531091144, 5.564688632600375, 5.560007634460565, 5.5341208139681735, 5.531232653234316, 5.5306168425081506, 5.522465810081427, 5.517719619624761, 5.5083329246238515, 5.497897604217482, 5.512902481355584, 5.499119833547444, 5.4895476790615625, 5.489571211628298, 5.473091678642293, 5.473188373570481, 5.460219620058963, 5.476024075961296, 5.470288747653901, 5.453687152389773, 5.446649695614507, 5.462309748583179, 5.466919067061493, 5.470036341946793, 5.438347108957569, 5.427130778937709, 5.408286381319185, 5.411183367895764, 5.399823924200682, 5.382030790078069, 5.3975284198180615, 5.387615523212358, 5.376516984351205, 5.369447200852689, 5.355805532420442, 5.365753240779986, 5.357173700504832, 5.3711908581838035, 5.364607314627925, 5.433981549429546, 5.455404415231483, 5.473992897265954, 5.472320881491823, 5.4719632714096935, 5.476632205566075, 5.474331012100722, 5.495380910808951, 5.497474437089139, 5.492423224248083, 5.491708009708677, 5.490755602112551, 5.485223669555997, 5.49118126503793, 5.490001825325303, 5.491296408442756, 5.481020685782744, 5.474390260945269, 5.495568682822753, 5.514122995982654, 5.513592751407556, 5.540095319969726, 5.537896912597121, 5.534918739823261, 5.535320987343045, 5.528671612829325, 5.517487305862701, 5.483992509874145, 5.482514149319509, 5.478092448232365, 5.484001080295465, 5.485915066662519, 5.482825261634958, 5.483004688710105, 5.47846041328191, 5.463449520180138, 5.458125490058336, 5.450474324325853, 5.448097853731051, 5.462982810484286, 5.425569544334078, 5.425687660587983, 5.419934520191085, 5.414850638430441, 5.4004134614953045, 5.40980018351596, 5.378470914740507, 5.366891854293412, 5.359333634366625, 5.352411324003998, 5.34541765189618, 5.333630080820168, 5.326636124335438, 5.309445267070314, 5.273309153130323, 5.25505047274526, 5.24618541990863, 5.238370832725811, 5.255972313827, 5.248672777354335, 5.250424049472827, 5.247223628732994, 5.239288464697034, 5.20811544594796, 5.2225054897089915, 5.201817198205308, 5.190367889157601, 5.181351520841264, 5.168151716794534, 5.167696910870011, 5.152760330675861, 5.147550679812072, 5.14866975658295, 5.166356881379743, 5.165694537365043, 5.17008128290847, 5.168357775845364, 5.1620673337078875, 5.178313569630496, 5.181157242513989, 5.17419852987691, 5.166846517594342, 5.157084820494451, 5.143050685261674, 5.135777545441203, 5.150518512979534, 5.1058675082567, 5.104350490129148, 5.101798241988393, 5.107956065811457, 5.107456022182829, 5.105341239741539, 5.1083274366464275, 5.112744500199421, 5.117502685504015, 5.108629255705154, 5.1201346775300784, 5.119284910481002, 5.118681197642752, 5.11255052742606, 5.085504109020481, 5.089229480412363, 5.08518158562253, 5.084978685593642, 5.085927425495169, 4.999572042707309, 5.023904886202018, 5.008036573582296, 5.027567740029482, 5.031999532131794, 5.026315112797118, 5.040125447845971, 5.046569163112369, 5.05081649914118, 5.059738151327985, 5.074176626603824, 5.083024519023251, 5.075329902000398, 5.022767626737198, 5.0433542573095025, 5.041890307523401, 5.063631579715184, 5.014533551723481, 5.038355667889924, 5.0647700442023, 5.096543618293296, 5.121124598815134, 5.1410098410340845, 5.167604662222939, 5.187691985531665, 5.188122357235683, 5.210521922319007, 5.230441692753906, 5.2494656814782505, 5.257550616227443, 5.269584286545293, 5.279389312167764, 5.30258360681187, 5.327194183883955, 5.340262006540561, 5.335648041728213, 5.347026827898066, 5.3545884449420225, 5.376256060558452, 5.3691438612053775, 5.373242833885306, 5.389508563282272, 5.391678216815697, 5.4306629951623515, 5.446635746981339, 5.466050815460057, 5.4747865158656905, 5.484334471206089, 5.424563899127167, 5.442030020047804, 5.4640970127264445, 5.477026942268272, 5.48772396784584, 5.4995395132115, 5.50550643429678, 5.516307113049071, 5.548262549201568, 5.550807801523168, 5.554704599706839, 5.560328152623787, 5.573980784803075, 5.569375475834278, 5.601701479515525, 5.619755377017445, 5.628516202564837, 5.625754008715117, 5.633958958630351, 5.652573532351871, 5.640497231113678, 5.650875912579764, 5.6833784708386785, 5.692377738663652, 5.689773955954845, 5.684371916784295, 5.683433973728549, 5.695703357203872, 5.701732732669949, 5.705749376604385, 5.718401623265783, 5.7378689437367205, 5.743995576139821, 5.755775182624349, 5.768134634252302, 5.769295075219144, 5.79095909013237, 5.804940170061114, 5.822457521242366, 5.832908088753179, 5.840462718503978, 5.842734980733307, 5.8499224377116015, 5.837763706080523, 5.847694250836154, 5.85662401058687, 5.859864506382134, 5.85463916822806, 5.85020950561822, 5.838894751058735, 5.8449605560255495, 5.849627786825189, 5.849106041437355, 5.850502342566311, 5.880595545991578, 5.8808753325339795, 5.874554514010439, 5.859751635816432, 5.861924894919394, 5.862495211116574, 5.8647388717254865, 5.852729412419459, 5.847140834074697, 5.837029609599973, 5.826177630160673, 5.807191589527041, 5.808946936187012, 5.807367758786735, 5.818181098976, 5.815722659116313, 5.817760571686709, 5.819274830124006, 5.815575315609071, 5.894137054525471, 5.891150063314729, 5.906404805531615, 5.896380996973665, 5.90257581974383, 5.919999355654732, 5.919187755341813, 5.9174329112212085, 5.914975094064423, 5.91389879040911, 5.917745282231594, 5.9085714714568445, 5.932922306958379, 6.00127036402336, 6.007777046137587, 6.029021568469031, 6.038037747900945, 6.102835192664666, 6.118407475199652, 6.116792242111362, 6.115138880857069, 6.131317564351513, 6.138447417744161, 6.1415902437958225, 6.14949220878296, 6.137740285473186, 6.155611005932229, 6.161816405002194, 6.168740903944049, 6.184523332314027, 6.178007582636205, 6.198962502633066, 6.202456708872725, 6.209622282748768, 6.20975022968066, 6.232469870509343, 6.250150167108279, 6.262358024950983, 6.2603833779951135, 6.285515472482946, 6.2928840206692795, 6.305088372564565, 6.328400268033306, 6.331219128431875, 6.334700673941661, 6.338622965699434, 6.331576903625838, 6.328633835364724, 6.423102679578676, 6.426877120078018, 6.435358223810196, 6.4405396376637825, 6.450256675734898, 6.453025338416355, 6.4572090666798605, 6.462841252114092, 6.474557823303671, 6.482618378221226, 6.492888679099717, 6.493415253923792, 6.495776927144424, 6.519298357147111, 6.520161163642501, 6.522970619704967, 6.520685174300075, 6.532202109031418, 6.5360478853251145, 6.521851984707042, 6.539850377800827, 6.540617470143408, 6.54315415263407, 6.555042347798956, 6.565436029906965, 6.587792109110902, 6.590041034392856, 6.598035000285633, 6.603046035831825, 6.604595665345233, 6.600511298955446, 6.610085521825074, 6.6137004040114595, 6.624182959964342, 6.627475250576547, 6.644257297184681, 6.639051304550646, 6.631813082824942, 6.637999053871295, 6.645424760700641, 6.644834485817655, 6.6508231470132815, 6.6355340032966605, 6.645341955095903, 6.63549669629638, 6.622946016581549, 6.61524971785107, 6.610406950735488, 6.611353809487648, 6.626171765788711, 6.627094679082473, 6.618417700322628, 6.615121890351678, 6.603645118741359, 6.605285421796017, 6.604894013220132, 6.607133099850246, 6.615634786481503, 6.622698171227061, 6.615842280262167, 6.607312048984789, 6.608272315252842, 6.608315622691214, 6.608648698923932, 6.6032104570839, 6.6051918307389625, 6.597623596787092, 6.595439654777665, 6.6081674624190505, 6.605752689801405, 6.594247766442498, 6.5948328728561085, 6.580231816426601, 6.574638832104039, 6.57801287671496, 6.579101982845528, 6.557189266138439, 6.546428993233472, 6.54061972468676, 6.540286774669196, 6.542733584930638, 6.55426554632191, 6.553829527414898, 6.545068778948574, 6.55722267153255, 6.559227696927114, 6.559180985156985, 6.547212956810423, 6.552834239223049, 6.545802303819574, 6.5562587865346655, 6.55288913204859, 6.561106811614433, 6.571782245494484, 6.555682602810491, 6.547695316898299, 6.5387255201224885, 6.535641699157091, 6.573708467188498, 6.561004391564758, 6.556932075983257, 6.564738857760856, 6.573309091932045, 6.590637664420894, 6.582308301214007, 6.5923471033216625, 6.582452963174254, 6.587208039091804, 6.601849473644657, 6.594727752979858, 6.593150143059064, 6.60579401805208, 6.615261736798919, 6.628203537929225, 6.628884326726014, 6.6338237723462035, 6.644736473374135, 6.652624667487359, 6.656889781203909, 6.681718947517043, 6.695084366411844, 6.679661202105258, 6.678517169108627, 6.648341949939299, 6.661869211382632, 6.657648868998093, 6.659289162204802, 6.655473002537723, 6.661475407268668, 6.660556088971073, 6.662196202829474, 6.653693722550027, 6.676388906360204, 6.679367052789723, 6.682000230970587, 6.684499304330604, 6.686856059361425, 6.68906228255251, 6.69110976039332, 6.692990279373315, 6.694695625981955, 6.6962175867087, 6.697547948043012, 6.698678496474351, 6.699601018492177, 6.70030730058595, 6.700789129245132, 6.701038290959182, 6.701046572217561, 6.70080575950973, 6.700307639325148, 6.699543998153277, 6.698506622483576, 6.697187298805507, 6.695577813608529, 6.693669953382104, 6.69145550461569, 6.68892625379875, 6.686073987420744, 6.682890491971131, 6.679367553939373, 6.675496959814929, 6.671270496087261, 6.666679949245828, 6.661717105780092, 6.656373752179511, 6.650641674933548, 6.644512660531663, 6.637978495463315, 6.631030966217966, 6.623661859285075, 6.615862961154105, 6.607626058314513, 6.598942937255762, 6.5898053844673115, 6.580205186438622, 6.570134129659154, 6.559584000618368, 6.548546585805724, 6.5370136717106835, 6.524977044822705, 6.512428491631252, 6.4993597986257825, 6.4857627522957575, 6.471629139130638, 6.456950745619883, 6.441719358252954, 6.425926763519313, 6.4095647479084175, 6.392625097909731, 6.37509960001271, 6.356980040706818, 6.338258206481515, 6.318925883826261] 28 | 29 | """Compute nodes""" 30 | FOG_CU = 400000 # CUs are equivalent to MIPS (million instructions per second) in this experiment 31 | FOG_MAX_POWER = 240 32 | FOG_STATIC_POWER = 100 33 | FOG_UTILIZATION_THRESHOLD = 0.85 34 | 35 | CLOUD_CU = np.inf 36 | CLOUD_WATT_PER_CU = 700e-6 37 | 38 | """Network (Latency is only used for determining shortest paths in the routing)""" 39 | WAN_BANDWIDTH = np.inf 40 | WAN_LATENCY = 100 41 | WAN_WATT_PER_BIT_UP = 6658e-9 42 | WAN_WATT_PER_BIT_DOWN = 20572e-9 43 | 44 | WIFI_BANDWIDTH = 1.6e9 45 | WIFI_LATENCY = 10 46 | WIFI_TAXI_TO_TL_WATT_PER_BIT = 300e-9 47 | WIFI_TL_TO_TL_WATT_PER_BIT = 100e-9 48 | WIFI_RANGE = 300 # e.g. Cisco Aironet 1570 Series 49 | 50 | ETHERNET_BANDWIDTH = 1e9 51 | ETHERNET_LATENCY = 1 52 | ETHERNET_WATT_PER_BIT = 0 53 | 54 | """Applications""" 55 | CCTV_PROCESSOR_CU = 30000 56 | CCTV_SOURCE_TO_PROCESSOR_BIT_RATE = 10e6 57 | CCTV_PROCESSOR_TO_SINK_BIT_RATE = 200e3 58 | 59 | V2I_PROCESSOR_CU = 7000 60 | V2I_SOURCE_TO_PROCESSOR_BIT_RATE = 100e3 61 | V2I_PROCESSOR_TO_SINK_BIT_RATE = 50e3 62 | -------------------------------------------------------------------------------- /leaf/application.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from typing import List, Tuple, Type, Optional, TypeVar, Union 3 | 4 | import networkx as nx 5 | 6 | from leaf.infrastructure import Node, Link 7 | from leaf.power import PowerAware, PowerMeasurement 8 | 9 | 10 | class Task(PowerAware): 11 | def __init__(self, cu: float): 12 | """Task that can be placed on a :class:`Node`. 13 | 14 | Tasks _can_ be connected via :class:`Link`s to build an :class:`Application`. 15 | 16 | Args: 17 | cu: Amount of compute units (CU) required to execute the task. CUs a imaginary unit for computational 18 | power to express differences between hardware platforms. 19 | 20 | Million instructions per second required to execute the task. 21 | """ 22 | self.id: Optional[int] = None 23 | self.cu = cu 24 | self.node: Optional[Node] = None 25 | 26 | def __repr__(self): 27 | return f"{self.__class__.__name__}(id={self.id}, cu={self.cu})" 28 | 29 | def allocate(self, node: Node): 30 | """Place the task on a certain node and allocate resources.""" 31 | if self.node is not None: 32 | raise ValueError(f"Cannot place {self} on {node}: It was already placed on {self.node}.") 33 | self.node = node 34 | self.node._add_task(self) 35 | 36 | def deallocate(self): 37 | """Detache the task from the node it is currently placed on and deallocate resources.""" 38 | if self.node is None: 39 | raise ValueError(f"{self} is not placed on any node.") 40 | self.node._remove_task(self) 41 | self.node = None 42 | 43 | def measure_power(self) -> PowerMeasurement: 44 | try: 45 | return self.node.measure_power().multiply(self.cu / self.node.used_cu) 46 | except ZeroDivisionError: 47 | return PowerMeasurement(0, 0) 48 | 49 | 50 | class SourceTask(Task): 51 | def __init__(self, cu: float = 0, bound_node: Node = None): 52 | """Source task of an application that is bound to a certain node, e.g. a sensor generating data. 53 | 54 | Source tasks never have incoming and always have outgoing data flows. 55 | 56 | Args: 57 | cu: Million instructions per second required to execute the task. 58 | bound_node: The node which the task is bound to. Cannot be None. 59 | """ 60 | super().__init__(cu) 61 | if bound_node is None: 62 | raise ValueError("bound_node for SourceTask cannot be None") 63 | self.bound_node = bound_node 64 | 65 | 66 | class ProcessingTask(Task): 67 | def __init__(self, cu: float = 0): 68 | """Processing task of an application that can be freely placed on the infrastructure. 69 | 70 | Processing tasks always have incoming and outgoing data flows. 71 | 72 | Args: 73 | cu: Million instructions per second required to execute the task. 74 | """ 75 | super().__init__(cu) 76 | 77 | 78 | class SinkTask(Task): 79 | def __init__(self, cu: float = 0, bound_node: Node = None): 80 | """Sink task of an application that is bound to a certain node, e.g. a cloud server for storage. 81 | 82 | Args: 83 | cu: Million instructions per second required to execute the task. 84 | bound_node: The node which the task is bound to. Cannot be None. 85 | """ 86 | super().__init__(cu) 87 | if bound_node is None: 88 | raise ValueError("bound_node for SourceTask cannot be None") 89 | self.bound_node = bound_node 90 | 91 | 92 | class DataFlow(PowerAware): 93 | def __init__(self, bit_rate: float): 94 | """Data flow between two tasks of an application. 95 | 96 | Args: 97 | bit_rate: The bit rate of the data flow in bit/s 98 | """ 99 | self.bit_rate = bit_rate 100 | self.links: Optional[List[Link]] = None 101 | 102 | def __repr__(self): 103 | return f"{self.__class__.__name__}(bit_rate={self.bit_rate})" 104 | 105 | def allocate(self, links: List[Link]): 106 | """Place the data flow on a path of links and allocate bandwidth.""" 107 | if self.links is not None: 108 | raise ValueError(f"Cannot place {self} on {links}: It was already placed on path {self.links}.") 109 | self.links = links 110 | for link in self.links: 111 | link._add_data_flow(self) 112 | 113 | def deallocate(self): 114 | """Remove the data flow from the infrastructure and deallocate bandwidth.""" 115 | if self.links is None: 116 | raise ValueError(f"{self} is not placed on any link.") 117 | for link in self.links: 118 | link._remove_data_flow(self) 119 | self.links = None 120 | 121 | def measure_power(self) -> PowerMeasurement: 122 | if self.links is None: 123 | raise RuntimeError("Cannot measure power: DataFlow was not placed on any links.") 124 | return PowerMeasurement.sum(link.measure_power().multiply(self.bit_rate / link.used_bandwidth) 125 | for link in self.links) 126 | 127 | 128 | class Application(PowerAware): 129 | """Application consisting of one or more tasks forming a directed acyclic graph (DAG).""" 130 | _TTask = TypeVar("TTask", bound=Task) # Generics 131 | _TDataFlow = TypeVar("TDataFlow", bound=DataFlow) # Generics 132 | _TaskTypeFilter = Union[Type[_TTask], Tuple[Type[_TTask], ...]] 133 | _DataFlowTypeFilter = Union[Type[_TDataFlow], Tuple[Type[_TDataFlow], ...]] 134 | 135 | def __init__(self): 136 | self.graph = nx.DiGraph() 137 | 138 | def __repr__(self): 139 | return f"{self.__class__.__name__}(tasks={len(self.tasks())})" 140 | 141 | def add_task(self, task: Task, incoming_data_flows: List[Tuple[Task, float]] = None): 142 | """Add a task to the application graph. 143 | 144 | Args: 145 | task: The task to add 146 | incoming_data_flows: List of tuples (`src_task`, `bit_rate`) where every `src_task` is the source of a 147 | :class:`DataFlow` with a certain `bit_rate` to the added `task` 148 | """ 149 | task.id = len(self.tasks()) 150 | if isinstance(task, SourceTask): 151 | assert not incoming_data_flows, f"Source task '{task}' cannot have incoming_data_flows" 152 | self.graph.add_node(task.id, data=task) 153 | elif isinstance(task, ProcessingTask): 154 | assert len(incoming_data_flows) > 0, f"Processing task '{task}' has no incoming_data_flows" 155 | self.graph.add_node(task.id, data=task) 156 | for src_task, bit_rate in incoming_data_flows: 157 | assert not isinstance(src_task, SinkTask), f"Sink task '{task}' cannot have outgoing data flows" 158 | self.graph.add_edge(src_task.id, task.id, data=DataFlow(bit_rate)) 159 | elif isinstance(task, SinkTask): 160 | assert len(incoming_data_flows) > 0, f"Sink task '{task}' has no incoming_data_flows" 161 | self.graph.add_node(task.id, data=task) 162 | for src_task, bit_rate in incoming_data_flows: 163 | assert not isinstance(src_task, SinkTask), f"Sink task '{task}' cannot have outgoing data flows" 164 | self.graph.add_edge(src_task.id, task.id, data=DataFlow(bit_rate)) 165 | assert nx.is_directed_acyclic_graph(self.graph), f"Application '{self}' is no DAG" 166 | else: 167 | raise ValueError(f"Unknown task type '{type(task)}'") 168 | 169 | def tasks(self, type_filter: Optional[_TaskTypeFilter] = None) -> List[_TTask]: 170 | """Return all tasks in the application, optionally filtered by class.""" 171 | task_iter = (task for _, task in self.graph.nodes.data("data")) 172 | if type_filter: 173 | task_iter = (task for task in task_iter if isinstance(task, type_filter)) 174 | return list(task_iter) 175 | 176 | def data_flows(self, type_filter: Optional[_DataFlowTypeFilter] = None) -> List[_TDataFlow]: 177 | """Return all data flows in the application, optionally filtered by class.""" 178 | df_iter = [v for _, _, v in self.graph.edges.data("data")] 179 | if type_filter: 180 | df_iter = (df for df in df_iter if isinstance(df, type_filter)) 181 | return list(df_iter) 182 | 183 | def deallocate(self): 184 | """Detach/Unmap/Release an application from the infrastructure it is currently placed on.""" 185 | for task in self.tasks(): 186 | task.deallocate() 187 | for data_flow in self.data_flows(): 188 | data_flow.deallocate() 189 | 190 | def measure_power(self) -> PowerMeasurement: 191 | measurements = [t.measure_power() for t in self.tasks()] + [df.measure_power() for df in self.data_flows()] 192 | return PowerMeasurement.sum(measurements) 193 | -------------------------------------------------------------------------------- /leaf/infrastructure.py: -------------------------------------------------------------------------------- 1 | import math 2 | from typing import List, Optional, Type, TypeVar, Iterator, Union, Tuple 3 | 4 | import networkx as nx 5 | 6 | from leaf.power import PowerAware, PowerMeasurement 7 | from leaf.mobility import Location 8 | 9 | 10 | class Node(PowerAware): 11 | def __init__(self, name: str, 12 | cu: Optional[float] = None, 13 | power_model: Optional["PowerModelNode"] = None, 14 | location: Optional[Location] = None): 15 | """A compute node in the infrastructure graph. 16 | 17 | This can represent any kind of node, e.g. 18 | - simple sensors without processing capabilities 19 | - resource constrained nodes fog computing nodes 20 | - mobile nodes like cars or smartphones 21 | - entire data centers with virtually unlimited resources 22 | 23 | Args: 24 | name: Name of the node. This is used to refer to nodes when defining links. 25 | cu: Maximum processing power the node provides in "compute units", a imaginary unit for computational power 26 | to express differences between hardware platforms. If None, the node has unlimited processing power. 27 | power_model: Power model which determines the power usage of the node. 28 | location: The (x,y) coordinates of the node 29 | """ 30 | self.name = name 31 | if cu is None: 32 | self.cu = math.inf 33 | else: 34 | self.cu = cu 35 | self.used_cu = 0 36 | self.tasks: List["Task"] = [] 37 | 38 | if power_model: 39 | if cu is None and power_model.max_power is not None: 40 | raise ValueError("Cannot use PowerModelNode with `max_power` on a compute node with unlimited " 41 | "processing power") 42 | self.power_model = power_model 43 | self.power_model.set_parent(self) 44 | 45 | self.location = location 46 | 47 | def __repr__(self): 48 | cu_repr = self.cu if self.cu is not None else "∞" 49 | return f"{self.__class__.__name__}('{self.name}', cu={self.used_cu}/{cu_repr})" 50 | 51 | def utilization(self) -> float: 52 | """Return the current utilization of the resource in the range [0, 1].""" 53 | try: 54 | return self.used_cu / self.cu 55 | except ZeroDivisionError: 56 | assert self.used_cu == 0 57 | return 0 58 | 59 | def _add_task(self, task: "Task"): 60 | """Add a task to the node. 61 | 62 | Private as this is only called by leaf.application.Task and not part of the public interface. 63 | """ 64 | self._reserve_cu(task.cu) 65 | self.tasks.append(task) 66 | 67 | def _remove_task(self, task: "Task"): 68 | """Remove a task from the node. 69 | 70 | Private as this is only called by leaf.application.Task and not part of the public interface. 71 | """ 72 | self._release_cu(task.cu) 73 | self.tasks.remove(task) 74 | 75 | def measure_power(self) -> PowerMeasurement: 76 | try: 77 | return self.power_model.measure() 78 | except AttributeError: 79 | return PowerMeasurement(0, 0) 80 | 81 | def _reserve_cu(self, cu: float): 82 | new_used_cu = self.used_cu + cu 83 | if new_used_cu > self.cu: 84 | raise ValueError(f"Cannot reserve {cu} CU on compute node {self}.") 85 | self.used_cu = new_used_cu 86 | 87 | def _release_cu(self, cu: float): 88 | new_used_cu = self.used_cu - cu 89 | if new_used_cu < 0: 90 | raise ValueError(f"Cannot release {cu} CU on compute node {self}.") 91 | self.used_cu = new_used_cu 92 | 93 | 94 | class Link(PowerAware): 95 | def __init__(self, src: Node, dst: Node, bandwidth: float, power_model: "PowerModelLink", latency: float = 0): 96 | """A network link in the infrastructure graph. 97 | 98 | This can represent any kind of network link, e.g. 99 | 100 | - direct cable connections 101 | - wireless connections such as WiFi, Bluetooth, LoRaWAN, 4G LTE, 5G, ... 102 | - entire wide area network connections that incorporate different networking equipment you do not want to 103 | model explicitly. 104 | 105 | Args: 106 | src: Source node of the network link. 107 | dst: Target node of the network link. 108 | bandwidth: Bandwidth provided by the network link. 109 | power_model: Power model which determines the power usage of the link. 110 | latency: Latency of the network link which can be used to implement routing policies. 111 | """ 112 | self.src = src 113 | self.dst = dst 114 | self.bandwidth = bandwidth 115 | self.latency = latency 116 | self.used_bandwidth = 0 117 | self.power_model = power_model 118 | self.power_model.set_parent(self) 119 | self.data_flows: List["DataFlow"] = [] 120 | 121 | def __repr__(self): 122 | latency_repr = f", latency={self.latency}" if self.latency else "" 123 | return f"{self.__class__.__name__}('{self.src.name}' -> '{self.dst.name}', bandwidth={self.used_bandwidth}/{self.bandwidth}{latency_repr})" 124 | 125 | def _add_data_flow(self, data_flow: "DataFlow"): 126 | """Add a data flow to the link. 127 | 128 | Private as this is only called by leaf.application.DataFlow and not part of the public interface. 129 | """ 130 | self._reserve_bandwidth(data_flow.bit_rate) 131 | self.data_flows.append(data_flow) 132 | 133 | def _remove_data_flow(self, data_flow: "DataFlow"): 134 | """Remove a data flow from the link. 135 | 136 | Private as this is only called by leaf.application.DataFlow and not part of the public interface. 137 | """ 138 | self._release_bandwidth(data_flow.bit_rate) 139 | self.data_flows.remove(data_flow) 140 | 141 | def measure_power(self) -> PowerMeasurement: 142 | try: 143 | return self.power_model.measure() 144 | except AttributeError: 145 | return PowerMeasurement(0, 0) 146 | 147 | def _reserve_bandwidth(self, bandwidth): 148 | new_used_bandwidth = self.used_bandwidth + bandwidth 149 | if new_used_bandwidth > self.bandwidth: 150 | raise ValueError(f"Cannot reserve {bandwidth} bandwidth on network link {self}.") 151 | self.used_bandwidth = new_used_bandwidth 152 | 153 | def _release_bandwidth(self, bandwidth): 154 | new_used_bandwidth = self.used_bandwidth - bandwidth 155 | if new_used_bandwidth < 0: 156 | raise ValueError(f"Cannot release {bandwidth} bandwidth on network link {self}.") 157 | self.used_bandwidth = new_used_bandwidth 158 | 159 | 160 | class Infrastructure(PowerAware): 161 | _TNode = TypeVar("_TNode", bound=Node) # Generics 162 | _TLink = TypeVar("_TLink", bound=Link) # Generics 163 | _NodeTypeFilter = Union[Type[_TNode], Tuple[Type[_TNode], ...]] 164 | _LinkTypeFilter = Union[Type[_TLink], Tuple[Type[_TLink], ...]] 165 | 166 | def __init__(self): 167 | """Infrastructure graph of the simulated scenario. 168 | 169 | The infrastructure is a weighted, directed multigraph where every node contains a :class:`Node` and every edge 170 | between contains a :class:`Link`. 171 | """ 172 | self.graph = nx.MultiDiGraph() 173 | 174 | def node(self, node_name: str) -> Node: 175 | """Return a specific node by name.""" 176 | return self.graph.nodes[node_name]["data"] 177 | 178 | # TODO link() 179 | 180 | def add_link(self, link: Link): 181 | """Add a link to the infrastructure. Missing nodes will be added automatically.""" 182 | self.add_node(link.src) 183 | self.add_node(link.dst) 184 | self.graph.add_edge(link.src.name, link.dst.name, data=link, latency=link.latency) 185 | 186 | def add_node(self, node: Node): 187 | """Adds a node to the infrastructure.""" 188 | if node.name not in self.graph: 189 | self.graph.add_node(node.name, data=node) 190 | 191 | def remove_node(self, node: Node): 192 | """Removes a node from the infrastructure.""" 193 | self.graph.remove_node(node.name) 194 | 195 | def nodes(self, type_filter: Optional[_NodeTypeFilter] = None) -> List[_TNode]: 196 | """Return all nodes in the infrastructure, optionally filtered by class.""" 197 | nodes: Iterator[Node] = (v for _, v in self.graph.nodes.data("data")) 198 | if type_filter is not None: 199 | nodes = (node for node in nodes if isinstance(node, type_filter)) 200 | return list(nodes) 201 | 202 | def links(self, type_filter: Optional[_LinkTypeFilter] = None) -> List[_TLink]: 203 | """Return all links in the infrastructure, optionally filtered by class.""" 204 | links: Iterator[Link] = (v for _, _, v in self.graph.edges.data("data")) 205 | if type_filter is not None: 206 | links = (link for link in links if isinstance(link, type_filter)) 207 | return list(links) 208 | 209 | def measure_power(self) -> PowerMeasurement: 210 | measurements = [node.measure_power() for node in self.nodes()] + [link.measure_power() for link in self.links()] 211 | return PowerMeasurement.sum(measurements) 212 | 213 | -------------------------------------------------------------------------------- /leaf/mobility.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | class Location: 5 | def __init__(self, x: float, y: float): 6 | self.x = x 7 | self.y = y 8 | 9 | def distance(self, location: "Location") -> float: 10 | return math.sqrt((location.y - self.y) * (location.y - self.y) + (location.x - self.x) * (location.x - self.x)) 11 | 12 | def __eq__(self, other): 13 | return self.x == other.x and self.y == other.y 14 | 15 | def __hash__(self): 16 | return hash((self.x, self.y)) 17 | -------------------------------------------------------------------------------- /leaf/orchestrator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from abc import ABC, abstractmethod 3 | from functools import partial 4 | from typing import Callable, List 5 | 6 | import networkx as nx 7 | 8 | from leaf.application import ProcessingTask, Application, SourceTask, SinkTask 9 | from leaf.infrastructure import Infrastructure, Node 10 | 11 | ProcessingTaskPlacement = Callable[[ProcessingTask, Application, Infrastructure], Node] 12 | DataFlowPath = Callable[[nx.Graph, str, str], List[str]] 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class Orchestrator(ABC): 18 | def __init__(self, infrastructure: Infrastructure, shortest_path: DataFlowPath = None): 19 | """Orchestrator which is responsible for allocating/placing application tasks on the infrastructure. 20 | 21 | Args: 22 | infrastructure: The infrastructure graph which the orchestrator operates on. 23 | shortest_path: A function for determining shortest/optimal paths between nodes. 24 | This function is called for every data flow between nodes that have been placed on the infrastructure. 25 | It takes the infrastructure graph, the source node, and target node and maps it to the list of nodes 26 | on the path. Defaults to `networkx.shortest_path`. More algorithms can be found 27 | `here `_. 28 | """ 29 | self.infrastructure = infrastructure 30 | if shortest_path is None: 31 | self.shortest_path = partial(nx.shortest_path, weight="latency") 32 | 33 | def place(self, application: Application): 34 | """Place an application on the infrastructure.""" 35 | logger.info(f"Placing {application}:") 36 | for task in application.tasks(): 37 | if isinstance(task, (SourceTask, SinkTask)): 38 | node = task.bound_node 39 | elif isinstance(task, ProcessingTask): 40 | node = self._processing_task_placement(task, application) 41 | else: 42 | raise TypeError(f"Unknown task type {task}") 43 | logger.info(f"- {task} on {node}.") 44 | task.allocate(node) 45 | 46 | for src_task_id, dst_task_id, data_flow in application.graph.edges.data("data"): 47 | src_task = application.graph.nodes[src_task_id]["data"] 48 | dst_task = application.graph.nodes[dst_task_id]["data"] 49 | shortest_path = self.shortest_path(self.infrastructure.graph, src_task.node.name, dst_task.node.name) 50 | links = [self.infrastructure.graph.edges[a, b, 0]["data"] for a, b in nx.utils.pairwise(shortest_path)] 51 | logger.info(f"- {data_flow} on {links}.") 52 | data_flow.allocate(links) 53 | 54 | @abstractmethod 55 | def _processing_task_placement(self, processing_task: ProcessingTask, application: Application) -> Node: 56 | pass 57 | -------------------------------------------------------------------------------- /leaf/power.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import math 3 | from abc import ABC, abstractmethod 4 | from functools import reduce 5 | from typing import List, Union, Collection, Callable, Optional, Iterable 6 | 7 | import simpy 8 | 9 | logger = logging.getLogger(__name__) 10 | _unnamed_power_meters_created = 0 11 | 12 | 13 | class PowerMeasurement: 14 | def __init__(self, dynamic: float, static: float): 15 | """Power measurement of one or more entities at a certain point in time. 16 | 17 | Args: 18 | dynamic: Dynamic (load-dependent) power usage in Watt 19 | static: Static (load-independent) power usage in Watt 20 | """ 21 | self.dynamic = dynamic 22 | self.static = static 23 | 24 | @classmethod 25 | def sum(cls, measurements: Iterable["PowerMeasurement"]): 26 | dynamic, static = reduce(lambda acc, cur: (acc[0] + cur.dynamic, acc[1] + cur.static), measurements, (0, 0)) 27 | return PowerMeasurement(dynamic, static) 28 | 29 | def __repr__(self): 30 | return f"PowerMeasurement(dynamic={self.dynamic:.2f}W, static={self.static:.2f}W)" 31 | 32 | def __float__(self) -> float: 33 | return float(self.dynamic + self.static) 34 | 35 | def __int__(self) -> float: 36 | return int(self.dynamic + self.static) 37 | 38 | def __add__(self, other): 39 | return PowerMeasurement(self.dynamic + other.dynamic, self.static + other.static) 40 | 41 | def __radd__(self, other): # Required for sum() 42 | if other == 0: 43 | return self 44 | else: 45 | return self.__add__(other) 46 | 47 | def __sub__(self, other): 48 | return PowerMeasurement(self.dynamic - other.dynamic, self.static - other.static) 49 | 50 | def multiply(self, factor: float): 51 | return PowerMeasurement(self.dynamic * factor, self.static * factor) 52 | 53 | def total(self) -> float: 54 | return float(self) 55 | 56 | 57 | class PowerModel(ABC): 58 | """Abstract base class for power models.""" 59 | 60 | # TODO: Validator! Only one power model per entity 61 | 62 | @abstractmethod 63 | def measure(self) -> PowerMeasurement: 64 | """Return the current power usage.""" 65 | 66 | @abstractmethod 67 | def set_parent(self, parent): 68 | """Set the entity which the power model is responsible for. 69 | 70 | Should be called in the parent's `__init__()`. 71 | """ 72 | 73 | 74 | class PowerModelNode(PowerModel): 75 | def __init__(self, max_power: float = None, power_per_cu: float = None, static_power: float = 0): 76 | """Power model for compute nodes with static and dynamic power usage. 77 | 78 | Power usage is scaled linearly with resource usage. 79 | 80 | Example: 81 | A computer which constantly uses 10 Watt even when being idle (`static_power=10`) but can consume 82 | up to 150 Watt when under full load (`max_power=150`). 83 | 84 | Args: 85 | max_power: Maximum power usage of the node under full load. Cannot be combined with `power_per_cu`. 86 | power_per_cu: Incremental power usage for each used compute unit. Cannot be combined with `max_power`. 87 | static_power: Idle power usage of the node without any load. 88 | """ 89 | if max_power is None and power_per_cu is None: 90 | raise ValueError("Either `max_power` or `power_per_cu` have to be stated.") 91 | if max_power is not None and power_per_cu is not None: 92 | raise ValueError("The parameters `max_power` or `power_per_cu` cannot be combined.") 93 | self.max_power = max_power 94 | self.power_per_cu = power_per_cu 95 | self.static_power = static_power 96 | self.node = None 97 | 98 | def measure(self) -> PowerMeasurement: 99 | if self.max_power is not None: 100 | dynamic_power = (self.max_power - self.static_power) * self.node.utilization() 101 | elif self.power_per_cu is not None: 102 | dynamic_power = self.power_per_cu * self.node.used_cu 103 | else: 104 | raise RuntimeError("Invalid state of PowerModelNode: `max_power` and `power_per_cu` are undefined.") 105 | return PowerMeasurement(dynamic=dynamic_power, static=self.static_power) 106 | 107 | def set_parent(self, parent): 108 | self.node = parent 109 | 110 | 111 | class PowerModelLink(PowerModel): 112 | def __init__(self, energy_per_bit: float): 113 | """Power model for network links. 114 | 115 | Args: 116 | energy_per_bit: Incremental energy per bit in J/bit (or W/(bit/s)) 117 | """ 118 | self.energy_per_bit = energy_per_bit 119 | self.link = None 120 | 121 | def measure(self) -> PowerMeasurement: 122 | dynamic_power = self.energy_per_bit * self.link.used_bandwidth 123 | return PowerMeasurement(dynamic=dynamic_power, static=0) 124 | 125 | def set_parent(self, parent): 126 | self.link = parent 127 | 128 | 129 | class PowerModelLinkWirelessTx(PowerModel): 130 | def __init__(self, energy_per_bit: float, amplifier_dissipation: float): 131 | """Power model for transmitting on wireless network links. 132 | 133 | TODO Explain 134 | 135 | Note: 136 | If you don't know the amplifier dissipation or distance of nodes or if you are concerned with performance, 137 | you can also just use the regular :class:`PowerModelLink` 138 | 139 | Args: 140 | energy_per_bit: Incremental energy per bit in J/bit (or W/(bit/s)) 141 | amplifier_dissipation: Amplifier energy dissipation in free space channel in J/bit/m^2 142 | """ 143 | self.energy_per_bit = energy_per_bit 144 | self.amplifier_dissipation = amplifier_dissipation 145 | self.link = None 146 | 147 | def measure(self) -> PowerMeasurement: 148 | distance = self.link.src.distance(self.link.dst) 149 | dissipation_energy_per_bit = self.amplifier_dissipation * distance ** 2 150 | dynamic_power = (self.energy_per_bit + dissipation_energy_per_bit) * self.link.used_bandwidth 151 | return PowerMeasurement(dynamic=dynamic_power, static=0) 152 | 153 | def set_parent(self, parent): 154 | self.link = parent 155 | 156 | 157 | class PowerAware(ABC): 158 | """Abstract base class for entites whose power can be measured. 159 | 160 | This may be parts of the infrastructure as well as applications. 161 | """ 162 | 163 | @abstractmethod 164 | def measure_power(self) -> PowerMeasurement: 165 | """Returns the power that is currently used by the entity.""" 166 | 167 | 168 | class PowerMeter: 169 | """Power meter that stores the power of one or more entites in regular intervals. 170 | 171 | Args: 172 | entities: Can be either (1) a single :class:`PowerAware` entity (2) a list of :class:`PowerAware` entities 173 | (3) a function which returns a list of :class:`PowerAware` entities, if the number of these entities 174 | changes during the simulation. 175 | name: Name of the power meter for logging and reporting 176 | measurement_interval: The freequency in which measurement take place. 177 | callback: A function which will be called with the PowerMeasurement result after each conducted measurement. 178 | """ 179 | def __init__(self, 180 | entities: Union[PowerAware, Collection[PowerAware], Callable[[], Collection[PowerAware]]], 181 | name: Optional[str] = None, 182 | measurement_interval: Optional[float] = 1, 183 | callback: Optional[Callable[[PowerMeasurement], None]] = None): 184 | self.entities = entities 185 | if name is None: 186 | global _unnamed_power_meters_created 187 | self.name = f"power_meter_{_unnamed_power_meters_created}" 188 | _unnamed_power_meters_created += 1 189 | else: 190 | self.name = name 191 | self.measurement_interval = measurement_interval 192 | self.callback = callback 193 | self.measurements = [] 194 | 195 | def run(self, env: simpy.Environment, delay: Optional[float] = 0): 196 | """Starts the power meter process. 197 | 198 | Args: 199 | env: Simpy environment (for timing the measurements) 200 | delay: The delay after which the measurements shall be conducted. For some scenarios it makes sense to e.g. 201 | include a tiny delay to make sure that all events at a previous time step were processed before the 202 | measurement is conducted. 203 | 204 | Returns: 205 | sim 206 | """ 207 | yield env.timeout(delay) 208 | while True: 209 | if isinstance(self.entities, PowerAware): 210 | measurement = self.entities.measure_power() 211 | else: 212 | if isinstance(self.entities, Collection): 213 | entities = self.entities 214 | elif isinstance(self.entities, Callable): 215 | entities = self.entities() 216 | else: 217 | raise ValueError(f"{self.name}: Unsupported type {type(self.entities)} for observable={self.entities}.") 218 | measurement = PowerMeasurement.sum(entity.measure_power() for entity in entities) 219 | self.measurements.append(measurement) 220 | if self.callback is not None: 221 | self.callback(measurement) 222 | logger.debug(f"{env.now}: {self.name}: {measurement}") 223 | yield env.timeout(self.measurement_interval) 224 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | here = os.path.abspath(os.path.dirname(__file__)) 5 | with open(os.path.join(here, "README.md"), encoding="utf-8") as f: 6 | long_description = "\n" + f.read() 7 | 8 | if __name__ == "__main__": 9 | setup( 10 | name='leafsim', 11 | use_scm_version=True, 12 | author="Philipp Wiesner", 13 | author_email="wiesner@tu-berlin.de", 14 | description="Simulator for modeling energy consumption in cloud, fog, and edge computing environments", 15 | long_description=long_description, 16 | long_description_content_type='text/markdown', 17 | keywords=["simulation", "modeling", "fog computing", "energy consumption", "edge computing"], 18 | url="https://github.com/dos-group/leaf", 19 | project_urls={ 20 | "Bug Tracker": "https://github.com/dos-group/leaf/issues", 21 | "Documentation": "https://leaf.readthedocs.io/", 22 | }, 23 | packages=["leaf"], 24 | license="MIT", 25 | python_requires=">=3.6", 26 | setup_requires=['setuptools_scm'], 27 | install_requires=[ 28 | 'networkx', 29 | 'numpy', 30 | 'pandas', 31 | 'simpy', 32 | 'tqdm', 33 | ], 34 | extras_require={ 35 | "docs": ["sphinx", "alabaster"] 36 | }, 37 | classifiers=[ 38 | "Development Status :: 3 - Alpha", 39 | "Intended Audience :: Education", 40 | "Intended Audience :: Science/Research", 41 | "License :: OSI Approved :: MIT License", 42 | "Operating System :: OS Independent", 43 | "Programming Language :: Python", 44 | "Programming Language :: Python :: 3.6", 45 | "Programming Language :: Python :: 3.7", 46 | "Programming Language :: Python :: 3.8", 47 | "Programming Language :: Python :: 3.9", 48 | "Programming Language :: Python :: 3.10", 49 | "Programming Language :: Python :: 3.11", 50 | "Topic :: Education", 51 | "Topic :: Scientific/Engineering", 52 | "Topic :: Scientific/Engineering :: Information Analysis", 53 | "Topic :: Software Development :: Libraries :: Python Modules", 54 | "Topic :: System :: Distributed Computing", 55 | "Typing :: Typed", 56 | ], 57 | ) 58 | --------------------------------------------------------------------------------