jupedsim-network 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jupedsim_network-0.1.0/.github/workflows/ci.yml +66 -0
- jupedsim_network-0.1.0/.gitignore +11 -0
- jupedsim_network-0.1.0/LICENSE +14 -0
- jupedsim_network-0.1.0/PKG-INFO +66 -0
- jupedsim_network-0.1.0/README.md +53 -0
- jupedsim_network-0.1.0/pyproject.toml +41 -0
- jupedsim_network-0.1.0/scripts/figures/make_figures.py +442 -0
- jupedsim_network-0.1.0/scripts/figures/make_gifs.py +291 -0
- jupedsim_network-0.1.0/scripts/figures/scenarios.py +100 -0
- jupedsim_network-0.1.0/site/assets/css/custom.css +6 -0
- jupedsim_network-0.1.0/site/content/_index.md +31 -0
- jupedsim_network-0.1.0/site/content/docs/_index.md +53 -0
- jupedsim_network-0.1.0/site/content/docs/evacuationz.md +39 -0
- jupedsim_network-0.1.0/site/content/docs/getting-started.md +169 -0
- jupedsim_network-0.1.0/site/content/docs/limitations.md +152 -0
- jupedsim_network-0.1.0/site/content/docs/model/_index.md +43 -0
- jupedsim_network-0.1.0/site/content/docs/model/elements.md +169 -0
- jupedsim_network-0.1.0/site/content/docs/model/update-scheme.md +153 -0
- jupedsim_network-0.1.0/site/content/docs/references.md +77 -0
- jupedsim_network-0.1.0/site/content/docs/ten-storey.md +50 -0
- jupedsim_network-0.1.0/site/content/docs/using/_index.md +14 -0
- jupedsim_network-0.1.0/site/content/docs/using/networks.md +146 -0
- jupedsim_network-0.1.0/site/content/docs/using/populations.md +111 -0
- jupedsim_network-0.1.0/site/content/docs/using/results.md +83 -0
- jupedsim_network-0.1.0/site/content/docs/using/running.md +186 -0
- jupedsim_network-0.1.0/site/content/docs/verification.md +137 -0
- jupedsim_network-0.1.0/site/go.mod +5 -0
- jupedsim_network-0.1.0/site/go.sum +2 -0
- jupedsim_network-0.1.0/site/hugo.yaml +55 -0
- jupedsim_network-0.1.0/site/layouts/alias.html +29 -0
- jupedsim_network-0.1.0/site/layouts/shortcodes/cite.html +20 -0
- jupedsim_network-0.1.0/src/jupedsim_network/__init__.py +42 -0
- jupedsim_network-0.1.0/src/jupedsim_network/hydraulic.py +62 -0
- jupedsim_network-0.1.0/src/jupedsim_network/network.py +267 -0
- jupedsim_network-0.1.0/src/jupedsim_network/sampling.py +109 -0
- jupedsim_network-0.1.0/src/jupedsim_network/simulation.py +482 -0
- jupedsim_network-0.1.0/tests/test_network.py +310 -0
- jupedsim_network-0.1.0/uv.lock +1183 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- run: uv run ruff check .
|
|
20
|
+
- run: uv run ruff format --check .
|
|
21
|
+
- run: uv run pytest
|
|
22
|
+
|
|
23
|
+
docs:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
env:
|
|
26
|
+
HUGO_VERSION: 0.144.0
|
|
27
|
+
steps:
|
|
28
|
+
- name: Install Hugo CLI
|
|
29
|
+
run: |
|
|
30
|
+
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
|
|
31
|
+
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
|
|
32
|
+
- uses: actions/setup-go@v5
|
|
33
|
+
with:
|
|
34
|
+
go-version: "1.22"
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
with:
|
|
37
|
+
fetch-depth: 0 # full history for enableGitInfo (last-updated dates)
|
|
38
|
+
- uses: astral-sh/setup-uv@v6
|
|
39
|
+
# The figures are not in git: regenerate them from the current code,
|
|
40
|
+
# so the site never shows a figure the code can no longer produce.
|
|
41
|
+
- name: Generate figures
|
|
42
|
+
run: |
|
|
43
|
+
uv run --group docs python scripts/figures/make_figures.py
|
|
44
|
+
uv run --group docs python scripts/figures/make_gifs.py
|
|
45
|
+
- name: Build with Hugo
|
|
46
|
+
env:
|
|
47
|
+
HUGO_ENVIRONMENT: production
|
|
48
|
+
working-directory: site
|
|
49
|
+
run: hugo --minify --panicOnWarning
|
|
50
|
+
- uses: actions/upload-pages-artifact@v3
|
|
51
|
+
with:
|
|
52
|
+
path: site/public
|
|
53
|
+
|
|
54
|
+
deploy:
|
|
55
|
+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
56
|
+
needs: [test, docs]
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
permissions:
|
|
59
|
+
pages: write
|
|
60
|
+
id-token: write
|
|
61
|
+
environment:
|
|
62
|
+
name: github-pages
|
|
63
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
64
|
+
steps:
|
|
65
|
+
- id: deployment
|
|
66
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
License:
|
|
2
|
+
========
|
|
3
|
+
jupedsim-network is free software: you can redistribute it and/or modify
|
|
4
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
5
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
any later version.
|
|
7
|
+
|
|
8
|
+
jupedsim-network is distributed in the hope that it will be useful,
|
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
GNU General Public License for more details.
|
|
12
|
+
|
|
13
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
14
|
+
along with jupedsim-network. If not, see <http://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: jupedsim-network
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Coarse network egress model for fast evacuation-time estimates, companion to JuPedSim
|
|
5
|
+
Project-URL: Documentation, https://pedestriandynamics.org/jupedsim-network/
|
|
6
|
+
Project-URL: Source, https://github.com/PedestrianDynamics/jupedsim-network
|
|
7
|
+
Project-URL: JuPedSim, https://www.jupedsim.org
|
|
8
|
+
License-Expression: LGPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: numpy>=1.26
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# jupedsim-network
|
|
15
|
+
|
|
16
|
+
A coarse network egress model for fast estimates of evacuation times
|
|
17
|
+
(pre-movement plus movement). Rooms, corridors and stair flights are
|
|
18
|
+
nodes with an area; doors and stair entries are links with a flow
|
|
19
|
+
capacity. Agents move
|
|
20
|
+
through the graph by relations adapted from the SFPE hydraulic model. A run of a
|
|
21
|
+
ten-storey building takes about 0.1 s, so scenarios can be sampled many
|
|
22
|
+
times.
|
|
23
|
+
|
|
24
|
+
The package is a companion to [JuPedSim](https://www.jupedsim.org): use
|
|
25
|
+
the network model to screen scenarios and find critical cases, and the
|
|
26
|
+
microscopic models of JuPedSim to check geometry details. It depends only
|
|
27
|
+
on NumPy.
|
|
28
|
+
|
|
29
|
+
The model is a prototype. It is verified against hand calculations but
|
|
30
|
+
not validated against experiments, and it is not intended for
|
|
31
|
+
regulatory or design use; see the
|
|
32
|
+
[limitations](https://pedestriandynamics.org/jupedsim-network/docs/limitations/).
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
Python 3.10 or later:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
pip install git+https://github.com/PedestrianDynamics/jupedsim-network
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
uv sync
|
|
46
|
+
uv run pytest
|
|
47
|
+
uv run ruff check . && uv run ruff format --check .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The documentation is a [Hugo](https://gohugo.io) site with the
|
|
51
|
+
[Hextra](https://imfing.github.io/hextra/) theme in `site/`. Hugo
|
|
52
|
+
(extended) and Go are needed to build it:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
uv run --group docs python scripts/figures/make_figures.py
|
|
56
|
+
uv run --group docs python scripts/figures/make_gifs.py
|
|
57
|
+
cd site && hugo server
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The figures and animations are not stored in git. The two scripts write
|
|
61
|
+
them to `site/static/images/network/` (about 2 minutes); CI runs them
|
|
62
|
+
before every build.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
LGPL-3.0-or-later, as JuPedSim.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# jupedsim-network
|
|
2
|
+
|
|
3
|
+
A coarse network egress model for fast estimates of evacuation times
|
|
4
|
+
(pre-movement plus movement). Rooms, corridors and stair flights are
|
|
5
|
+
nodes with an area; doors and stair entries are links with a flow
|
|
6
|
+
capacity. Agents move
|
|
7
|
+
through the graph by relations adapted from the SFPE hydraulic model. A run of a
|
|
8
|
+
ten-storey building takes about 0.1 s, so scenarios can be sampled many
|
|
9
|
+
times.
|
|
10
|
+
|
|
11
|
+
The package is a companion to [JuPedSim](https://www.jupedsim.org): use
|
|
12
|
+
the network model to screen scenarios and find critical cases, and the
|
|
13
|
+
microscopic models of JuPedSim to check geometry details. It depends only
|
|
14
|
+
on NumPy.
|
|
15
|
+
|
|
16
|
+
The model is a prototype. It is verified against hand calculations but
|
|
17
|
+
not validated against experiments, and it is not intended for
|
|
18
|
+
regulatory or design use; see the
|
|
19
|
+
[limitations](https://pedestriandynamics.org/jupedsim-network/docs/limitations/).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Python 3.10 or later:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install git+https://github.com/PedestrianDynamics/jupedsim-network
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Development
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
uv sync
|
|
33
|
+
uv run pytest
|
|
34
|
+
uv run ruff check . && uv run ruff format --check .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The documentation is a [Hugo](https://gohugo.io) site with the
|
|
38
|
+
[Hextra](https://imfing.github.io/hextra/) theme in `site/`. Hugo
|
|
39
|
+
(extended) and Go are needed to build it:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
uv run --group docs python scripts/figures/make_figures.py
|
|
43
|
+
uv run --group docs python scripts/figures/make_gifs.py
|
|
44
|
+
cd site && hugo server
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The figures and animations are not stored in git. The two scripts write
|
|
48
|
+
them to `site/static/images/network/` (about 2 minutes); CI runs them
|
|
49
|
+
before every build.
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
LGPL-3.0-or-later, as JuPedSim.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jupedsim-network"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Coarse network egress model for fast evacuation-time estimates, companion to JuPedSim"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "LGPL-3.0-or-later"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["numpy>=1.26"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Documentation = "https://pedestriandynamics.org/jupedsim-network/"
|
|
17
|
+
Source = "https://github.com/PedestrianDynamics/jupedsim-network"
|
|
18
|
+
JuPedSim = "https://www.jupedsim.org"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
22
|
+
docs = ["matplotlib==3.10.8"]
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/jupedsim_network"]
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
testpaths = ["tests"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 80
|
|
32
|
+
indent-width = 4
|
|
33
|
+
target-version = "py310"
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint]
|
|
36
|
+
select = ["E", "F", "I", "N"]
|
|
37
|
+
ignore = ["E501"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff.format]
|
|
40
|
+
quote-style = "double"
|
|
41
|
+
indent-style = "space"
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Static figures for the network model documentation.
|
|
2
|
+
|
|
3
|
+
Writes PNGs to ``site/static/images/network`` or to the directory given as
|
|
4
|
+
the first argument. ``jupedsim_network`` must be importable::
|
|
5
|
+
|
|
6
|
+
uv run --group docs python scripts/figures/make_figures.py
|
|
7
|
+
uv run --group docs python scripts/figures/make_gifs.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import matplotlib
|
|
14
|
+
|
|
15
|
+
matplotlib.use("Agg")
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
import numpy as np
|
|
18
|
+
from matplotlib.patches import Circle, FancyArrowPatch, FancyBboxPatch
|
|
19
|
+
from scenarios import building, building_populations, merge_corridor
|
|
20
|
+
|
|
21
|
+
from jupedsim_network import (
|
|
22
|
+
LogNormal,
|
|
23
|
+
Network,
|
|
24
|
+
NetworkSimulation,
|
|
25
|
+
Population,
|
|
26
|
+
hydraulic,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
30
|
+
DEFAULT_OUT = ROOT / "site" / "static" / "images" / "network"
|
|
31
|
+
PAL = ["#90c1c6", "#72a5b4", "#58849f", "#446485", "#324465", "#1f253f"]
|
|
32
|
+
RED = "#bd0c0c"
|
|
33
|
+
ORANGE = "#fc8d59"
|
|
34
|
+
GREY = "dimgrey"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def style():
|
|
38
|
+
plt.rcParams.update(
|
|
39
|
+
{
|
|
40
|
+
"font.family": "DejaVu Sans",
|
|
41
|
+
"font.size": 10,
|
|
42
|
+
"axes.spines.top": False,
|
|
43
|
+
"axes.spines.right": False,
|
|
44
|
+
"axes.spines.left": False,
|
|
45
|
+
"axes.spines.bottom": False,
|
|
46
|
+
"axes.grid": False,
|
|
47
|
+
"axes.labelcolor": "#333333",
|
|
48
|
+
"axes.titlesize": 11,
|
|
49
|
+
"axes.titleweight": "semibold",
|
|
50
|
+
"xtick.major.size": 0,
|
|
51
|
+
"ytick.major.size": 0,
|
|
52
|
+
"xtick.color": GREY,
|
|
53
|
+
"ytick.color": GREY,
|
|
54
|
+
"legend.frameon": True,
|
|
55
|
+
"legend.facecolor": "white",
|
|
56
|
+
"legend.framealpha": 0.8,
|
|
57
|
+
"legend.edgecolor": "lightgrey",
|
|
58
|
+
"legend.labelcolor": GREY,
|
|
59
|
+
"savefig.dpi": 150,
|
|
60
|
+
"savefig.bbox": "tight",
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def frame(*axes):
|
|
66
|
+
for ax in axes:
|
|
67
|
+
ax.patch.set_edgecolor("lightgrey")
|
|
68
|
+
ax.patch.set_linewidth(0.8)
|
|
69
|
+
ax.grid(alpha=0.4, linewidth=0.6)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def box(ax, xy, w, h, text, color):
|
|
73
|
+
patch = FancyBboxPatch(
|
|
74
|
+
xy,
|
|
75
|
+
w,
|
|
76
|
+
h,
|
|
77
|
+
boxstyle="round,pad=0.02,rounding_size=0.08",
|
|
78
|
+
fc=color,
|
|
79
|
+
ec="#324465",
|
|
80
|
+
lw=1.0,
|
|
81
|
+
)
|
|
82
|
+
ax.add_patch(patch)
|
|
83
|
+
ax.text(
|
|
84
|
+
xy[0] + w / 2,
|
|
85
|
+
xy[1] + h / 2,
|
|
86
|
+
text,
|
|
87
|
+
ha="center",
|
|
88
|
+
va="center",
|
|
89
|
+
fontsize=9,
|
|
90
|
+
color="#1f253f",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def arrow(ax, a, b, text=None, offset=(0, 0.12)):
|
|
95
|
+
ax.add_patch(
|
|
96
|
+
FancyArrowPatch(
|
|
97
|
+
a, b, arrowstyle="-|>", mutation_scale=12, color="#446485", lw=1.4
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
if text:
|
|
101
|
+
mid = ((a[0] + b[0]) / 2 + offset[0], (a[1] + b[1]) / 2 + offset[1])
|
|
102
|
+
ax.text(*mid, text, ha="center", va="bottom", fontsize=8, color=GREY)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def schematic(out):
|
|
106
|
+
fig, ax = plt.subplots(figsize=(10, 3.6))
|
|
107
|
+
box(ax, (0.0, 1.6), 2.2, 1.1, "", "#e8f1f2")
|
|
108
|
+
ax.text(
|
|
109
|
+
1.1,
|
|
110
|
+
2.48,
|
|
111
|
+
"room a, area $A_a$",
|
|
112
|
+
ha="center",
|
|
113
|
+
fontsize=9,
|
|
114
|
+
color="#1f253f",
|
|
115
|
+
)
|
|
116
|
+
box(ax, (0.0, 0.0), 2.2, 1.1, "room b\narea $A_b$", "#e8f1f2")
|
|
117
|
+
box(ax, (3.6, 0.8), 1.8, 1.1, "corridor\narea $A_c$", "#e8f1f2")
|
|
118
|
+
box(ax, (6.8, 0.8), 1.8, 1.1, "stair flight\n$k(R,T)$, area", "#d5e5ea")
|
|
119
|
+
box(ax, (9.8, 0.8), 1.4, 1.1, "safe", "#e9f4e4")
|
|
120
|
+
arrow(ax, (2.2, 2.15), (3.6, 1.55), "door, $w_a$")
|
|
121
|
+
arrow(ax, (2.2, 0.55), (3.6, 1.15), "door, $w_b$", offset=(0, -0.35))
|
|
122
|
+
arrow(ax, (5.4, 1.35), (6.8, 1.35), "stair entry")
|
|
123
|
+
arrow(ax, (8.6, 1.35), (9.8, 1.35), "door")
|
|
124
|
+
for x in range(5):
|
|
125
|
+
ax.add_patch(Circle((1.75 - 0.18 * x, 2.15), 0.06, color=RED))
|
|
126
|
+
ax.annotate(
|
|
127
|
+
"",
|
|
128
|
+
xy=(0.15, 1.92),
|
|
129
|
+
xytext=(2.05, 1.92),
|
|
130
|
+
arrowprops=dict(arrowstyle="<->", color=GREY, lw=0.8),
|
|
131
|
+
)
|
|
132
|
+
ax.text(
|
|
133
|
+
1.1,
|
|
134
|
+
1.7,
|
|
135
|
+
"link length $L$ (walked in source)",
|
|
136
|
+
ha="center",
|
|
137
|
+
fontsize=7.5,
|
|
138
|
+
color=GREY,
|
|
139
|
+
)
|
|
140
|
+
ax.text(
|
|
141
|
+
1.0, 2.15, "queue →", fontsize=7.5, color=RED, ha="right", va="center"
|
|
142
|
+
)
|
|
143
|
+
ax.text(
|
|
144
|
+
5.6,
|
|
145
|
+
-0.25,
|
|
146
|
+
r"link capacity $C = F_s\,(w - 2b)$ · node density "
|
|
147
|
+
r"$D = \sum_i a_i / A$",
|
|
148
|
+
ha="center",
|
|
149
|
+
fontsize=9,
|
|
150
|
+
color=GREY,
|
|
151
|
+
)
|
|
152
|
+
ax.set_xlim(-0.2, 11.4)
|
|
153
|
+
ax.set_ylim(-0.5, 2.9)
|
|
154
|
+
ax.set_aspect("equal")
|
|
155
|
+
ax.axis("off")
|
|
156
|
+
fig.savefig(out / "schematic.png")
|
|
157
|
+
plt.close(fig)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def mark_densities(ax, marks):
|
|
161
|
+
top = ax.get_ylim()[1] * 0.98
|
|
162
|
+
for x, text in marks:
|
|
163
|
+
ax.axvline(x, color="lightgrey", lw=0.8, zorder=0)
|
|
164
|
+
ax.text(
|
|
165
|
+
x,
|
|
166
|
+
top,
|
|
167
|
+
text,
|
|
168
|
+
fontsize=7.5,
|
|
169
|
+
color=GREY,
|
|
170
|
+
ha="center",
|
|
171
|
+
va="top",
|
|
172
|
+
bbox=dict(fc="white", ec="none", pad=1),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def fundamental(out):
|
|
177
|
+
d = np.linspace(0, hydraulic.JAM_DENSITY, 400)
|
|
178
|
+
k_level = hydraulic.LEVEL_SPEED_CONSTANT
|
|
179
|
+
k_stair = hydraulic.stair_speed_constant(0.18, 0.28)
|
|
180
|
+
fig, axes = plt.subplots(1, 3, figsize=(13, 3.8))
|
|
181
|
+
for k, label, color in [
|
|
182
|
+
(k_level, "level, k = 1.40 m/s", PAL[3]),
|
|
183
|
+
(k_stair, "stair 18/28 cm, k = 1.08 m/s", ORANGE),
|
|
184
|
+
]:
|
|
185
|
+
s = hydraulic.speed(d, k)
|
|
186
|
+
axes[0].plot(d, s, color=color, lw=2, label=label)
|
|
187
|
+
axes[1].plot(d, s * d, color=color, lw=2, label=label)
|
|
188
|
+
marks = [
|
|
189
|
+
(hydraulic.MIN_DENSITY, "0.54"),
|
|
190
|
+
(hydraulic.PEAK_FLOW_DENSITY, "1.88"),
|
|
191
|
+
(2.75, "2.75"),
|
|
192
|
+
(hydraulic.JAM_DENSITY, "3.76"),
|
|
193
|
+
]
|
|
194
|
+
for ax in axes[:2]:
|
|
195
|
+
mark_densities(ax, marks)
|
|
196
|
+
axes[0].set(
|
|
197
|
+
xlabel="density D (1/m²)",
|
|
198
|
+
ylabel="speed S (m/s)",
|
|
199
|
+
title="(a) speed, S = k (1 − 0.266 D)",
|
|
200
|
+
)
|
|
201
|
+
axes[1].set(
|
|
202
|
+
xlabel="density D (1/m²)",
|
|
203
|
+
ylabel="specific flow S·D (1/m/s)",
|
|
204
|
+
title="(b) specific flow",
|
|
205
|
+
)
|
|
206
|
+
axes[0].legend(loc="lower left", fontsize=8)
|
|
207
|
+
phi = np.clip((2.75 - d) / (2.75 - hydraulic.PEAK_FLOW_DENSITY), 0, 1)
|
|
208
|
+
axes[2].plot(d, phi, color=PAL[4], lw=2, label="supply reduction on")
|
|
209
|
+
axes[2].plot(
|
|
210
|
+
d,
|
|
211
|
+
np.where(d < 2.75, 1, 0),
|
|
212
|
+
color=RED,
|
|
213
|
+
lw=1.2,
|
|
214
|
+
ls="--",
|
|
215
|
+
label="hard limit only",
|
|
216
|
+
)
|
|
217
|
+
axes[2].set(
|
|
218
|
+
xlabel="density of receiving node D (1/m²)",
|
|
219
|
+
ylabel="accepted share of inflow φ",
|
|
220
|
+
title="(c) supply factor",
|
|
221
|
+
xlim=(0, hydraulic.JAM_DENSITY),
|
|
222
|
+
)
|
|
223
|
+
axes[2].legend(loc="lower left", fontsize=8)
|
|
224
|
+
axes[2].annotate(
|
|
225
|
+
"$D_{peak}$",
|
|
226
|
+
(1.88, 1.0),
|
|
227
|
+
(1.2, 0.7),
|
|
228
|
+
color=GREY,
|
|
229
|
+
arrowprops=dict(arrowstyle="-", color=GREY, lw=0.6),
|
|
230
|
+
)
|
|
231
|
+
axes[2].annotate(
|
|
232
|
+
"$D_{max}$",
|
|
233
|
+
(2.75, 0.0),
|
|
234
|
+
(3.0, 0.35),
|
|
235
|
+
color=GREY,
|
|
236
|
+
arrowprops=dict(arrowstyle="-", color=GREY, lw=0.6),
|
|
237
|
+
)
|
|
238
|
+
frame(*axes)
|
|
239
|
+
fig.tight_layout()
|
|
240
|
+
fig.savefig(out / "fundamental_diagram.png")
|
|
241
|
+
plt.close(fig)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def door_and_merge(out):
|
|
245
|
+
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
|
|
246
|
+
net = Network()
|
|
247
|
+
net.add_room("room", area=40.0)
|
|
248
|
+
net.add_safe("exit")
|
|
249
|
+
net.connect("room", "exit", width=1.0, specific_flow=1.33)
|
|
250
|
+
r = NetworkSimulation(net, [Population("room", 100, speed=1.0)]).run(1)
|
|
251
|
+
exits = np.sort(r.exit_times)
|
|
252
|
+
axes[0].step(
|
|
253
|
+
exits,
|
|
254
|
+
np.arange(1, 101),
|
|
255
|
+
where="post",
|
|
256
|
+
color=PAL[3],
|
|
257
|
+
lw=2,
|
|
258
|
+
label="network model, Δt = 0.5 s",
|
|
259
|
+
)
|
|
260
|
+
t = np.linspace(0, 99 / (1.33 * 0.7), 50)
|
|
261
|
+
axes[0].plot(
|
|
262
|
+
t,
|
|
263
|
+
1 + 1.33 * 0.7 * t,
|
|
264
|
+
color=RED,
|
|
265
|
+
ls="--",
|
|
266
|
+
lw=1.2,
|
|
267
|
+
label="model convention 1 + C t",
|
|
268
|
+
)
|
|
269
|
+
t = np.linspace(0, 100 / (1.33 * 0.7), 50)
|
|
270
|
+
axes[0].plot(
|
|
271
|
+
t,
|
|
272
|
+
1.33 * 0.7 * t,
|
|
273
|
+
color=ORANGE,
|
|
274
|
+
ls=":",
|
|
275
|
+
lw=1.6,
|
|
276
|
+
label="SFPE hand calculation C t",
|
|
277
|
+
)
|
|
278
|
+
axes[0].set(
|
|
279
|
+
xlabel="time (s)",
|
|
280
|
+
ylabel="agents through door",
|
|
281
|
+
title="(a) IMO test 4 geometry: 100 agents, 1 m door",
|
|
282
|
+
)
|
|
283
|
+
axes[0].text(
|
|
284
|
+
60,
|
|
285
|
+
12,
|
|
286
|
+
f"last agent: {r.evacuation_time:.1f} s\n"
|
|
287
|
+
f"(N − 1)/C = {99 / 0.931:.1f} s\n"
|
|
288
|
+
f"N/C = {100 / 0.931:.1f} s",
|
|
289
|
+
color=GREY,
|
|
290
|
+
fontsize=9,
|
|
291
|
+
)
|
|
292
|
+
axes[0].legend(loc="upper left", fontsize=8)
|
|
293
|
+
|
|
294
|
+
pops = [Population("a", 300), Population("b", 300)]
|
|
295
|
+
r = NetworkSimulation(merge_corridor(), pops, t_max=150.0).run(seed=2)
|
|
296
|
+
for name, color, w, ls in [
|
|
297
|
+
("a->corridor", PAL[2], 1, "-"),
|
|
298
|
+
("b->corridor", ORANGE, 3, "--"),
|
|
299
|
+
]:
|
|
300
|
+
flow = r.link_flow[:, r.link_names.index(name)]
|
|
301
|
+
axes[1].plot(
|
|
302
|
+
r.times,
|
|
303
|
+
np.cumsum(flow),
|
|
304
|
+
color=color,
|
|
305
|
+
ls=ls,
|
|
306
|
+
lw=2,
|
|
307
|
+
label=f"{name.split('-')[0]} (merge weight {w})",
|
|
308
|
+
)
|
|
309
|
+
axes[1].set(
|
|
310
|
+
xlabel="time (s)",
|
|
311
|
+
ylabel="agents into corridor",
|
|
312
|
+
title="(b) merging into a full corridor, weights 1 : 3",
|
|
313
|
+
)
|
|
314
|
+
axes[1].legend(loc="upper left", fontsize=8)
|
|
315
|
+
frame(*axes)
|
|
316
|
+
fig.tight_layout()
|
|
317
|
+
fig.savefig(out / "door_and_merge.png")
|
|
318
|
+
plt.close(fig)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def rset_quantiles(max_density, supply_reduction, runs):
|
|
322
|
+
sim = NetworkSimulation(
|
|
323
|
+
building(stairs=("A",)),
|
|
324
|
+
building_populations(),
|
|
325
|
+
max_density=max_density,
|
|
326
|
+
supply_reduction=supply_reduction,
|
|
327
|
+
)
|
|
328
|
+
times = sim.run_many(runs, seed=1).evacuation_times
|
|
329
|
+
return np.quantile(times, [0.05, 0.5, 0.95])
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def density_sweep(out, runs=30):
|
|
333
|
+
limits = np.array([2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.7])
|
|
334
|
+
fig, ax = plt.subplots(figsize=(7, 4.2))
|
|
335
|
+
for on, color, label, marker in [
|
|
336
|
+
(True, PAL[3], "supply reduction on", "o"),
|
|
337
|
+
(False, RED, "hard limit only", "s"),
|
|
338
|
+
]:
|
|
339
|
+
q = np.array([rset_quantiles(m, on, runs) for m in limits])
|
|
340
|
+
ax.fill_between(limits, q[:, 0], q[:, 2], color=color, alpha=0.15)
|
|
341
|
+
ax.plot(limits, q[:, 1], color=color, marker=marker, lw=2, label=label)
|
|
342
|
+
ax.set(
|
|
343
|
+
xlabel="max_density (1/m²)",
|
|
344
|
+
ylabel="evacuation time (s), median and 5–95 %",
|
|
345
|
+
title="10 floors × 60 agents, one stair",
|
|
346
|
+
)
|
|
347
|
+
ax.legend(loc="upper left", fontsize=8)
|
|
348
|
+
frame(ax)
|
|
349
|
+
fig.savefig(out / "max_density_sweep.png")
|
|
350
|
+
plt.close(fig)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def route_tie(out):
|
|
354
|
+
cases = [
|
|
355
|
+
(
|
|
356
|
+
"two stairs, nearest exit (tie → one stair)",
|
|
357
|
+
building(),
|
|
358
|
+
building_populations(),
|
|
359
|
+
RED,
|
|
360
|
+
"-",
|
|
361
|
+
),
|
|
362
|
+
(
|
|
363
|
+
"two stairs, targets split 50/50",
|
|
364
|
+
building(),
|
|
365
|
+
building_populations(split="AB"),
|
|
366
|
+
PAL[3],
|
|
367
|
+
"--",
|
|
368
|
+
),
|
|
369
|
+
(
|
|
370
|
+
"one stair",
|
|
371
|
+
building(stairs=("A",)),
|
|
372
|
+
building_populations(),
|
|
373
|
+
GREY,
|
|
374
|
+
":",
|
|
375
|
+
),
|
|
376
|
+
]
|
|
377
|
+
fig, ax = plt.subplots(figsize=(7, 4.2))
|
|
378
|
+
for label, net, pops, color, ls in cases:
|
|
379
|
+
r = NetworkSimulation(net, pops).run(seed=1)
|
|
380
|
+
e = np.sort(r.exit_times)
|
|
381
|
+
ax.step(
|
|
382
|
+
e,
|
|
383
|
+
np.arange(1, len(e) + 1),
|
|
384
|
+
where="post",
|
|
385
|
+
color=color,
|
|
386
|
+
ls=ls,
|
|
387
|
+
lw=2,
|
|
388
|
+
label=f"{label}: {r.evacuation_time:.1f} s",
|
|
389
|
+
)
|
|
390
|
+
ax.set(
|
|
391
|
+
xlabel="time (s)",
|
|
392
|
+
ylabel="agents evacuated",
|
|
393
|
+
title="Equal-distance exits are not split",
|
|
394
|
+
)
|
|
395
|
+
ax.legend(loc="lower right", fontsize=8)
|
|
396
|
+
frame(ax)
|
|
397
|
+
fig.savefig(out / "route_tie.png")
|
|
398
|
+
plt.close(fig)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def monte_carlo(out, runs=300):
|
|
402
|
+
pre = LogNormal(120.0, 60.0, upper=600.0)
|
|
403
|
+
pops = building_populations(pre=pre, split="AB")
|
|
404
|
+
mc = NetworkSimulation(building(), pops).run_many(runs, seed=11)
|
|
405
|
+
t = mc.complete
|
|
406
|
+
fig, ax = plt.subplots(figsize=(7, 4.2))
|
|
407
|
+
ax.hist(t, bins=30, color=PAL[2], edgecolor="white")
|
|
408
|
+
for q, ls in [(0.5, "--"), (0.95, "-")]:
|
|
409
|
+
v = mc.quantile(q)
|
|
410
|
+
ax.axvline(v, color=RED, ls=ls, lw=1.2)
|
|
411
|
+
ax.text(
|
|
412
|
+
v,
|
|
413
|
+
ax.get_ylim()[1] * 0.95,
|
|
414
|
+
f" {int(q * 100)} %: {v:.0f} s",
|
|
415
|
+
color=RED,
|
|
416
|
+
fontsize=8.5,
|
|
417
|
+
va="top",
|
|
418
|
+
)
|
|
419
|
+
ax.set(
|
|
420
|
+
xlabel="evacuation time (s)",
|
|
421
|
+
ylabel="runs",
|
|
422
|
+
title=f"{runs} runs, pre-movement LogNormal(120 s, 60 s)",
|
|
423
|
+
)
|
|
424
|
+
ax.grid(alpha=0.4, axis="y")
|
|
425
|
+
fig.savefig(out / "monte_carlo.png")
|
|
426
|
+
plt.close(fig)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def main():
|
|
430
|
+
out = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_OUT
|
|
431
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
432
|
+
style()
|
|
433
|
+
schematic(out)
|
|
434
|
+
fundamental(out)
|
|
435
|
+
door_and_merge(out)
|
|
436
|
+
density_sweep(out)
|
|
437
|
+
route_tie(out)
|
|
438
|
+
monte_carlo(out)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
if __name__ == "__main__":
|
|
442
|
+
main()
|