├── .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 [](https://pypi.org/project/leafsim/) [](https://pypi.org/project/leafsim/) [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------