handoverlab 1.0.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.
Files changed (163) hide show
  1. handoverlab-1.0.0/.zenodo.json +26 -0
  2. handoverlab-1.0.0/CHANGELOG.md +338 -0
  3. handoverlab-1.0.0/CITATION.cff +32 -0
  4. handoverlab-1.0.0/CONTRIBUTING.md +80 -0
  5. handoverlab-1.0.0/LICENSE +21 -0
  6. handoverlab-1.0.0/MANIFEST.in +8 -0
  7. handoverlab-1.0.0/PKG-INFO +363 -0
  8. handoverlab-1.0.0/README.md +315 -0
  9. handoverlab-1.0.0/docs/RELEASING.md +100 -0
  10. handoverlab-1.0.0/docs/calibration.md +111 -0
  11. handoverlab-1.0.0/docs/guides/add-scenario.md +83 -0
  12. handoverlab-1.0.0/docs/guides/daps-ltm.md +145 -0
  13. handoverlab-1.0.0/docs/guides/dashboard.md +63 -0
  14. handoverlab-1.0.0/docs/guides/export-event-logs.md +63 -0
  15. handoverlab-1.0.0/docs/guides/highway.md +71 -0
  16. handoverlab-1.0.0/docs/guides/learning-rule.md +126 -0
  17. handoverlab-1.0.0/docs/guides/many-cores.md +76 -0
  18. handoverlab-1.0.0/docs/guides/reproduce.md +55 -0
  19. handoverlab-1.0.0/docs/guides/upper-mid-band.md +66 -0
  20. handoverlab-1.0.0/docs/images/dashboard-charts.png +0 -0
  21. handoverlab-1.0.0/docs/images/dashboard.png +0 -0
  22. handoverlab-1.0.0/docs/images/report-comparison.png +0 -0
  23. handoverlab-1.0.0/docs/images/report-tuning.png +0 -0
  24. handoverlab-1.0.0/docs/index.md +51 -0
  25. handoverlab-1.0.0/docs/performance.md +119 -0
  26. handoverlab-1.0.0/docs/reference/api/config.md +8 -0
  27. handoverlab-1.0.0/docs/reference/api/engine.md +23 -0
  28. handoverlab-1.0.0/docs/reference/api/eventlog.md +8 -0
  29. handoverlab-1.0.0/docs/reference/api/gui.md +13 -0
  30. handoverlab-1.0.0/docs/reference/api/index.md +8 -0
  31. handoverlab-1.0.0/docs/reference/api/rules.md +18 -0
  32. handoverlab-1.0.0/docs/reference/api/scenarios.md +8 -0
  33. handoverlab-1.0.0/docs/reference/api/simulate.md +13 -0
  34. handoverlab-1.0.0/docs/reference/api/stability.md +9 -0
  35. handoverlab-1.0.0/docs/reference/api/stats.md +8 -0
  36. handoverlab-1.0.0/docs/reference/api/studies.md +43 -0
  37. handoverlab-1.0.0/docs/reference/api/testing.md +8 -0
  38. handoverlab-1.0.0/docs/reference/api/verify.md +18 -0
  39. handoverlab-1.0.0/docs/reference/cli.md +171 -0
  40. handoverlab-1.0.0/docs/reference/metrics.md +60 -0
  41. handoverlab-1.0.0/docs/scenarios/fr3-urban-15ghz.md +91 -0
  42. handoverlab-1.0.0/docs/scenarios/fr3-urban-15ghz.svg +89 -0
  43. handoverlab-1.0.0/docs/scenarios/highway-10ghz.md +95 -0
  44. handoverlab-1.0.0/docs/scenarios/highway-10ghz.svg +10 -0
  45. handoverlab-1.0.0/docs/scenarios/ns3-urban-3.5ghz.md +91 -0
  46. handoverlab-1.0.0/docs/scenarios/ns3-urban-3.5ghz.svg +15 -0
  47. handoverlab-1.0.0/docs/scenarios/open-area-10ghz.md +90 -0
  48. handoverlab-1.0.0/docs/scenarios/open-area-10ghz.svg +126 -0
  49. handoverlab-1.0.0/docs/scenarios/smoke.md +91 -0
  50. handoverlab-1.0.0/docs/scenarios/smoke.svg +34 -0
  51. handoverlab-1.0.0/docs/scenarios/urban-grid-10ghz.md +92 -0
  52. handoverlab-1.0.0/docs/scenarios/urban-grid-10ghz.svg +89 -0
  53. handoverlab-1.0.0/docs/stability.md +79 -0
  54. handoverlab-1.0.0/docs/tutorials/first-run.md +128 -0
  55. handoverlab-1.0.0/docs/tutorials/own-rule.md +112 -0
  56. handoverlab-1.0.0/docs/tutorials/reading-the-report.md +116 -0
  57. handoverlab-1.0.0/docs/tutorials/tuning-study.md +176 -0
  58. handoverlab-1.0.0/docs/user_guide.md +707 -0
  59. handoverlab-1.0.0/docs/verification.md +162 -0
  60. handoverlab-1.0.0/examples/compare_a3_cho.py +19 -0
  61. handoverlab-1.0.0/examples/open_area.toml +37 -0
  62. handoverlab-1.0.0/examples/smoke.yaml +12 -0
  63. handoverlab-1.0.0/examples/speed_rule.py +47 -0
  64. handoverlab-1.0.0/examples/street_grid.yaml +24 -0
  65. handoverlab-1.0.0/examples/study_smoke.yaml +57 -0
  66. handoverlab-1.0.0/mkdocs.yml +88 -0
  67. handoverlab-1.0.0/pyproject.toml +88 -0
  68. handoverlab-1.0.0/setup.cfg +4 -0
  69. handoverlab-1.0.0/src/handoverlab/__init__.py +30 -0
  70. handoverlab-1.0.0/src/handoverlab/__main__.py +7 -0
  71. handoverlab-1.0.0/src/handoverlab/channel/__init__.py +8 -0
  72. handoverlab-1.0.0/src/handoverlab/channel/pathloss.py +122 -0
  73. handoverlab-1.0.0/src/handoverlab/channel/radiomap.py +198 -0
  74. handoverlab-1.0.0/src/handoverlab/channel/source.py +130 -0
  75. handoverlab-1.0.0/src/handoverlab/cli.py +334 -0
  76. handoverlab-1.0.0/src/handoverlab/config.py +448 -0
  77. handoverlab-1.0.0/src/handoverlab/engine/__init__.py +10 -0
  78. handoverlab-1.0.0/src/handoverlab/engine/batch.py +200 -0
  79. handoverlab-1.0.0/src/handoverlab/engine/core.py +546 -0
  80. handoverlab-1.0.0/src/handoverlab/engine/invariants.py +141 -0
  81. handoverlab-1.0.0/src/handoverlab/engine/metrics.py +107 -0
  82. handoverlab-1.0.0/src/handoverlab/engine/procedures.py +452 -0
  83. handoverlab-1.0.0/src/handoverlab/eventlog.py +335 -0
  84. handoverlab-1.0.0/src/handoverlab/gui/__init__.py +53 -0
  85. handoverlab-1.0.0/src/handoverlab/gui/app.py +139 -0
  86. handoverlab-1.0.0/src/handoverlab/gui/logic.py +466 -0
  87. handoverlab-1.0.0/src/handoverlab/io.py +79 -0
  88. handoverlab-1.0.0/src/handoverlab/layout.py +214 -0
  89. handoverlab-1.0.0/src/handoverlab/measurement.py +115 -0
  90. handoverlab-1.0.0/src/handoverlab/mobility.py +169 -0
  91. handoverlab-1.0.0/src/handoverlab/py.typed +0 -0
  92. handoverlab-1.0.0/src/handoverlab/rules/__init__.py +107 -0
  93. handoverlab-1.0.0/src/handoverlab/rules/base.py +249 -0
  94. handoverlab-1.0.0/src/handoverlab/rules/builtin.py +278 -0
  95. handoverlab-1.0.0/src/handoverlab/scenarios/__init__.py +247 -0
  96. handoverlab-1.0.0/src/handoverlab/scenarios/cards/fr3-urban-15ghz.yaml +33 -0
  97. handoverlab-1.0.0/src/handoverlab/scenarios/cards/highway-10ghz.yaml +39 -0
  98. handoverlab-1.0.0/src/handoverlab/scenarios/cards/ns3-urban-3.5ghz.yaml +52 -0
  99. handoverlab-1.0.0/src/handoverlab/scenarios/cards/open-area-10ghz.yaml +28 -0
  100. handoverlab-1.0.0/src/handoverlab/scenarios/cards/smoke.yaml +31 -0
  101. handoverlab-1.0.0/src/handoverlab/scenarios/cards/urban-grid-10ghz.yaml +27 -0
  102. handoverlab-1.0.0/src/handoverlab/seeds.py +40 -0
  103. handoverlab-1.0.0/src/handoverlab/simulate.py +141 -0
  104. handoverlab-1.0.0/src/handoverlab/stability.py +186 -0
  105. handoverlab-1.0.0/src/handoverlab/stats.py +249 -0
  106. handoverlab-1.0.0/src/handoverlab/studies/__init__.py +33 -0
  107. handoverlab-1.0.0/src/handoverlab/studies/config.py +464 -0
  108. handoverlab-1.0.0/src/handoverlab/studies/report.py +538 -0
  109. handoverlab-1.0.0/src/handoverlab/studies/runner.py +307 -0
  110. handoverlab-1.0.0/src/handoverlab/studies/status.py +223 -0
  111. handoverlab-1.0.0/src/handoverlab/studies/store.py +251 -0
  112. handoverlab-1.0.0/src/handoverlab/studies/study.py +559 -0
  113. handoverlab-1.0.0/src/handoverlab/studies/tuning.py +337 -0
  114. handoverlab-1.0.0/src/handoverlab/testing.py +171 -0
  115. handoverlab-1.0.0/src/handoverlab/traffic.py +30 -0
  116. handoverlab-1.0.0/src/handoverlab/verify/__init__.py +74 -0
  117. handoverlab-1.0.0/src/handoverlab/verify/data/golden.json +114 -0
  118. handoverlab-1.0.0/src/handoverlab/verify/data/ns3_urban_a3.json +1471 -0
  119. handoverlab-1.0.0/src/handoverlab/verify/engine_checks.py +105 -0
  120. handoverlab-1.0.0/src/handoverlab/verify/golden.py +126 -0
  121. handoverlab-1.0.0/src/handoverlab/verify/ns3.py +125 -0
  122. handoverlab-1.0.0/src/handoverlab/verify/radio.py +190 -0
  123. handoverlab-1.0.0/src/handoverlab/verify/report.py +74 -0
  124. handoverlab-1.0.0/src/handoverlab/verify/stored.py +70 -0
  125. handoverlab-1.0.0/src/handoverlab/world.py +55 -0
  126. handoverlab-1.0.0/src/handoverlab.egg-info/PKG-INFO +363 -0
  127. handoverlab-1.0.0/src/handoverlab.egg-info/SOURCES.txt +161 -0
  128. handoverlab-1.0.0/src/handoverlab.egg-info/dependency_links.txt +1 -0
  129. handoverlab-1.0.0/src/handoverlab.egg-info/entry_points.txt +2 -0
  130. handoverlab-1.0.0/src/handoverlab.egg-info/requires.txt +30 -0
  131. handoverlab-1.0.0/src/handoverlab.egg-info/top_level.txt +1 -0
  132. handoverlab-1.0.0/tests/conftest.py +15 -0
  133. handoverlab-1.0.0/tests/data/bh6_reference.json +325 -0
  134. handoverlab-1.0.0/tests/data/bh6_wide.json +2373 -0
  135. handoverlab-1.0.0/tests/data/make_bh6_reference.py +61 -0
  136. handoverlab-1.0.0/tests/data/make_bh6_wide.py +100 -0
  137. handoverlab-1.0.0/tests/data/public_api.json +513 -0
  138. handoverlab-1.0.0/tests/helpers.py +39 -0
  139. handoverlab-1.0.0/tests/test_batch.py +45 -0
  140. handoverlab-1.0.0/tests/test_config_cli.py +116 -0
  141. handoverlab-1.0.0/tests/test_contract.py +75 -0
  142. handoverlab-1.0.0/tests/test_determinism.py +46 -0
  143. handoverlab-1.0.0/tests/test_engine_triggers.py +108 -0
  144. handoverlab-1.0.0/tests/test_eventlog.py +92 -0
  145. handoverlab-1.0.0/tests/test_gui.py +140 -0
  146. handoverlab-1.0.0/tests/test_highway.py +99 -0
  147. handoverlab-1.0.0/tests/test_invariants.py +87 -0
  148. handoverlab-1.0.0/tests/test_levels_status_extend.py +196 -0
  149. handoverlab-1.0.0/tests/test_measurement.py +130 -0
  150. handoverlab-1.0.0/tests/test_pathloss.py +55 -0
  151. handoverlab-1.0.0/tests/test_regression_bh6.py +101 -0
  152. handoverlab-1.0.0/tests/test_regression_bh6_wide.py +85 -0
  153. handoverlab-1.0.0/tests/test_rules_rc2.py +212 -0
  154. handoverlab-1.0.0/tests/test_scenarios.py +71 -0
  155. handoverlab-1.0.0/tests/test_stability.py +124 -0
  156. handoverlab-1.0.0/tests/test_stats.py +131 -0
  157. handoverlab-1.0.0/tests/test_study.py +224 -0
  158. handoverlab-1.0.0/tests/test_tools.py +47 -0
  159. handoverlab-1.0.0/tests/test_tuning.py +128 -0
  160. handoverlab-1.0.0/tests/test_verify.py +88 -0
  161. handoverlab-1.0.0/tools/compare_fingerprints.py +131 -0
  162. handoverlab-1.0.0/tools/mkdocs_hooks.py +26 -0
  163. handoverlab-1.0.0/tools/release_check.py +98 -0
@@ -0,0 +1,26 @@
1
+ {
2
+ "title": "HandoverLab: system-level simulation of handover and mobility beyond 5G",
3
+ "description": "HandoverLab is an open-source, system-level simulator of 3GPP mobility procedures (baseline handover with Event A3, Release-16 conditional and DAPS handover, Release-18 L1/L2-triggered mobility) for many users in dense 5G and beyond-5G networks, on a street grid, in an open area or along a highway. It provides a TR 38.901 received-power radio model, an L3-filtered measurement chain, a radio-link failure model, an outcome label for every handover attempt, a plug-in interface for handover rules, equal-budget tuning studies with paired statistics, and verification against hand-computed values, golden event-log fingerprints and ns-3 / 5G-LENA.",
4
+ "upload_type": "software",
5
+ "access_right": "open",
6
+ "license": "MIT",
7
+ "creators": [
8
+ {"name": "Jalili Sanjarani, Mostafa"},
9
+ {"name": "Elmi Sola, Yasser"}
10
+ ],
11
+ "keywords": [
12
+ "handover",
13
+ "conditional handover",
14
+ "mobility",
15
+ "mobility robustness",
16
+ "radio-link failure",
17
+ "system-level simulation",
18
+ "5G",
19
+ "beyond 5G",
20
+ "3GPP",
21
+ "Python"
22
+ ],
23
+ "related_identifiers": [
24
+ {"identifier": "https://github.com/mjs19971997/handoverlab", "relation": "isSupplementTo", "scheme": "url"}
25
+ ]
26
+ }
@@ -0,0 +1,338 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file. The format follows
4
+ [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/) for the public surface described in
6
+ [docs/stability.md](docs/stability.md); before 1.0.0 any interface could change.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2026-10-16
11
+
12
+ First stable release (stages 5 to 8). This section merges the release candidates 1.0.0rc1 to
13
+ 1.0.0rc4, which were not uploaded to PyPI. From this release on the public surface follows the
14
+ stability policy in [docs/stability.md](docs/stability.md). Everything since 0.4.0a1 was added next
15
+ to what was there: the defaults, the results of the existing rules and scenarios, the golden
16
+ fingerprints and the bh6 regression are unchanged, and the model version stays 1.
17
+
18
+ ### Added
19
+
20
+ - Schema version of the file formats (`handoverlab.stability.SCHEMA_VERSION`, 1): experiment and
21
+ study files accept a top-level `schema` key, and result JSON files and `study.lock.json` carry
22
+ it. A file without the key is read as schema 1; a file with a newer schema is refused with a
23
+ message that names it. `handoverlab init`, the examples and the dashboard's experiment file
24
+ write `schema: 1`.
25
+ - Stability policy (`docs/stability.md`): what the public surface is (configuration keys, result
26
+ columns, metric names, the rule interface, the documented Python names and commands), what a
27
+ major, minor or patch release may change, and how deprecations work.
28
+ - `handoverlab.stability`: `RULE_API_VERSION` (1), `METRIC_ALIASES` with `metric_name` (an old
29
+ metric name keeps working with a warning; study files resolve metric names through it),
30
+ `deprecated` and `deprecated_alias` raising `HandoverLabDeprecationWarning` (a `FutureWarning`),
31
+ `check_schema`, and `public_surface`, a snapshot of the public names, rule hooks and record
32
+ fields, metrics, event and trace fields, and configuration keys. A test compares it with
33
+ `tests/data/public_api.json`, so that no part of the surface changes unnoticed.
34
+ - How-to guides: add a scenario, run a study on many cores, export event logs and traces, and
35
+ check results against the stored fingerprints.
36
+ - CI: a lint job (ruff, plus the release-metadata check) and a minimum total test coverage of
37
+ 85 % (90 % at this release) in the Linux / Python 3.12 test job; a scheduled nightly
38
+ workflow that runs the long verification on Linux and Windows and compares the golden
39
+ fingerprints of both systems with each other and with the reference.
40
+ - Release plumbing: a workflow that builds the sdist and the wheel, checks them and publishes
41
+ them to PyPI with trusted publishing when a version tag is pushed (or a GitHub release is
42
+ published); `.zenodo.json` for the archived DOI; `tools/release_check.py` (versions, changelog
43
+ and citation metadata agree, tag matches the version); `docs/RELEASING.md` with the steps.
44
+ - DAPS handover (Rel-16): rule base class `DAPSRule` (family `daps`, hooks of `BaselineRule`;
45
+ attributes `interruption_s`, `fallback`, `source_data`) and the built-in rule `daps` (`DAPS`).
46
+ The source keeps serving during the execution (interruption `interruption_s`, default 0); an
47
+ execution that cannot join the target falls back to the source if its SINR is at least Qout
48
+ (outcome `too_early`, new event-log cause `daps_fallback`, not a failure). With
49
+ `fallback: false, source_data: false` it equals `a3`.
50
+ - L1/L2-triggered mobility (Rel-18): rule base class `LTMRule` (family `ltm`, hooks `prep_offsets`
51
+ and `switch_offsets`; attributes `report_offset_db`, `ttt_prep_s`, `n_max`, `release_hys_db`,
52
+ `stick_db`, `ttt_switch_s`, `cmd_delay_s`, `exec_s`, `recovery`) and the built-in rule `ltm`
53
+ (`LTM`): candidates configured on L3 reports, cell switch decided by the network on unfiltered L1
54
+ measurements, cell switch command with its own delay and loss, short execution, optional recovery
55
+ into a candidate.
56
+ - A learning-based example rule `a3_bandit` (`BanditA3`): Event A3 whose offset every cell learns
57
+ with an epsilon-greedy bandit from the outcome labels; NumPy only.
58
+ - `Observation.l1_dbm`: the unfiltered L1 samples of the measurement instant (appended field with a
59
+ default, so existing rules and code that builds observations keep working).
60
+ - Built-in scenario `fr3-urban-15ghz`: the default grid in the upper mid-band at 15 GHz with a
61
+ 400 MHz carrier and 3.5 dB more beam gain, with its scenario card.
62
+ - Event-log kinds `daps` and `ltm`; trace events `switch` (LTM cell switch command) and `fallback`
63
+ (DAPS); `serving_cells` returns the source after a fallback.
64
+ - `handoverlab.rules.RULE_BASES` and `handoverlab.testing.FAMILIES`; the contract kit and
65
+ `register_rule` accept `DAPSRule` and `LTMRule` rules.
66
+ - Documentation: the procedures in the user guide; how-to guides for DAPS and LTM, for using and
67
+ writing a learning-based rule, and for the upper mid-band.
68
+ - Tests of the DAPS, LTM and bandit trigger and update logic on prescribed received powers, and
69
+ their contract checks.
70
+ - Layout kind `highway`: the straight multi-lane road of the TR 37.885 highway case along the
71
+ x axis, with the new keys `layout.lanes` (lanes per direction, default 3), `layout.lane_width_m`
72
+ (4 m) and `layout.road_length_m` (2000 m); roadside sites every `layout.site_spacing_m` metres,
73
+ `layout.site_offset_m` beyond the road edge, alternately on the two sides; no buildings, LOS
74
+ state and shadowing as in the open area. `handoverlab.layout.highway` builds it.
75
+ - Lane-keeping highway mobility: every vehicle keeps its lane and speed and at the end of the road
76
+ turns into the same lane of the other direction; the lateral range of a speed group is the
77
+ displacement from the lane centre (at most half a lane). Mobility profiles `hwy` (70-140 km/h),
78
+ `hwy70` (63-77 km/h) and `hwy140` (126-140 km/h).
79
+ - Built-in scenario `highway-10ghz` with its scenario card: 2000 m, 3 + 3 lanes of 4 m, four
80
+ roadside sites every 500 m, 35 m from the road edge, TR 38.901 UMa with 25 m masts, 40 vehicles
81
+ of the profile `hwy`.
82
+ - `layout.sites_file`: a CSV file with the columns `x` and `y` that fills `layout.sites` when the
83
+ scenario is loaded (`handoverlab.config.read_sites`), for every layout kind.
84
+ - `Scenario.to_dict(full=False)`: keys added after 1.0.0rc2 (`handoverlab.config.LATE_KEYS`) are
85
+ left out while they have their default value, so that result files and the job IDs and locks of
86
+ existing studies stay the same; `full=True` lists every key.
87
+ - Documentation: the highway in the user guide, the how-to guide [a highway](docs/guides/highway.md)
88
+ and the scenario card page.
89
+ - Tests of the highway geometry, lane keeping, speeds, the turn at the road end, a short run with
90
+ invariant checks, the omitted keys and site files.
91
+
92
+ ### Changed
93
+
94
+ - Version 1.0.0; development status Production/Stable.
95
+ - `CITATION.cff` gives the release version and date, and the preferred way to cite.
96
+ - The changelog links of the alpha releases point to their commits, so they resolve without
97
+ tags; later releases compare tags.
98
+ - A few long lines were wrapped for the lint job (no change of behaviour).
99
+ - The public-surface snapshot `tests/data/public_api.json` was extended deliberately: the hooks
100
+ and attributes of `DAPSRule` and `LTMRule`, the new exports (`DAPS`, `LTM`, `DAPSRule`,
101
+ `LTMRule`, `BanditA3`, `RULE_BASES`), the observation field `l1_dbm`, the trace events
102
+ `switch` and `fallback`, and the scenario keys `layout.lanes`, `layout.lane_width_m`,
103
+ `layout.road_length_m` and `layout.sites_file`. Nothing was removed or renamed.
104
+ - Engine internals (not public): a procedure may set its execution time, its user-plane
105
+ interruption, a fallback to the source and data on the source during the execution; the
106
+ conditional procedure is split into execution and reporting steps that the LTM procedure reuses.
107
+ Results of the existing procedures are bit-identical.
108
+ - `Scenario.with_overrides` accepts the new keys (it works on the full key set).
109
+ - Validation: `layout.kind` also accepts `highway`; `layout.lanes`, `layout.lane_width_m`,
110
+ `layout.road_length_m` and a non-empty `layout.sites` are checked.
111
+ - Publishing: the publish workflow also runs when a tag `v*` is pushed, checks that the tag
112
+ matches the version, and skips files that are already on PyPI, so that a GitHub release made
113
+ later from the same tag (for the Zenodo archive) does not fail. `docs/RELEASING.md` describes
114
+ the steps.
115
+ - README and documentation: installation from PyPI (`python -m pip install handoverlab`) next to
116
+ the installation from source.
117
+
118
+ ### Fixed
119
+
120
+ - Scenario cards: clause numbers checked against the specifications. The highway lanes, lane
121
+ width and road length are cited from TR 37.885 Table A-1 (not clause 6.1.2); the highway
122
+ inter-site distance of 500 m from TR 37.885 clause 6.1.3 (TR 38.913 gives it only as an
123
+ option); the highway speeds from clause 6.1.2 (140 km/h, optionally 70 km/h; the uniform
124
+ range between them is an assumption); the urban speed of 60 km/h from clause 6.1.2 (not
125
+ Annex A); the 50 m correlation distance of TR 38.901 Table 7.6.3.1-2 as that of the LOS/NLOS
126
+ state (UMi and UMa); the 4 dB LOS shadowing as that of UMi and UMa. The 35 m site offset of
127
+ `highway-10ghz` is an assumption, after the freeway set-up of TR 36.885 clause A.1.3.
128
+ - Documentation: the list of built-in scenarios in the first tutorial, the highway in the home
129
+ page and the README guide list, the versions named in the user guide and the verification
130
+ page, the command synopsis (`verify --ns3-seeds`, `init --force`), a snippet of the user guide
131
+ that used an undefined name, the per-carrier tuning of the upper mid-band guide (it needs
132
+ `tuning.group_by`), and the release steps (all release-candidate sections are merged).
133
+ - Metadata: the descriptions in `CITATION.cff` and `.zenodo.json` name all procedures and
134
+ layouts; links to the documentation site, which is not published, were removed or point to
135
+ the `docs/` folder.
136
+
137
+ ## [0.4.0a1] - 2026-10-15
138
+
139
+ Fourth alpha release (stage 4: dashboard, documentation, study tooling).
140
+
141
+ ### Added
142
+
143
+ - Browser dashboard (extra `gui`: Streamlit and matplotlib; `handoverlab gui [--port] [--address]
144
+ [--headless]`): scenario and setting pickers (street grid or open area, blocks, site spacing,
145
+ carrier frequency, path loss, users, speed profile, run length, warm-up, seeds), up to four A3 or
146
+ CHO rules with their parameters, a run button with progress, a map of the users on the layout
147
+ coloured by serving cell (time slider and play, with counts up to the chosen time), charts of
148
+ handovers, unnecessary handovers, failures, failure causes, delivered rate and cumulative events,
149
+ a metrics table, and downloads of metrics (CSV), results (JSON), event logs and traces (CSV),
150
+ figures (PNG) and an experiment file that `handoverlab run` repeats exactly. It runs locally
151
+ only. The logic (`handoverlab.gui.logic`) needs no Streamlit and is tested on its own; the page
152
+ is tested headless with Streamlit's test runner.
153
+ - Event-log levels `off`, `summary` and `full` (`log_events` in `simulate`, `run_world` and
154
+ `Engine`; `run --events FILE --event-level full`; `events: off | summary | full` in study files).
155
+ Level `full` adds a procedure trace (`RunResult.trace`): initial serving cell, measurement
156
+ reports, preparations, releases, configurations reaching the UE or lost, executions,
157
+ attachments after a handover or a re-establishment, and failures; `TRACE_FIELDS`,
158
+ `TRACE_EVENTS`, `write_trace`, `read_trace`, `trace_path` and `serving_cells` (serving cell of
159
+ every user at given times). Study test stages at level `full` store a trace table per run.
160
+ - `run_world(progress=...)`: a callback with the share of the run simulated so far.
161
+ - `handoverlab status STUDY|DIR [--workers N]` and `handoverlab.studies.status_data`: planned,
162
+ finished, failed and remaining jobs per stage, rule runs still to simulate, gate state, sessions
163
+ and the time left, estimated from the study's finished jobs (world-build time and engine time per
164
+ rule run and user-second); stages not yet plannable are counted from the study file. A study
165
+ directory alone is enough (`load_study_dir` reads `study.lock.json`). `study --status` shows the
166
+ same.
167
+ - Gate policy `extend` for `grid_edge` with `tuning.extend_steps` (default 2) and
168
+ `tuning.max_extensions` (default 2): a grid whose selected value lies on its edge is widened by
169
+ values with the spacing of the last two (never below zero for a non-negative grid, and only with
170
+ values the rule accepts), every method is tuned again with the same budget (stored rule runs are
171
+ reused), and this repeats up to the cap, after which a remaining edge blocks. Extensions are
172
+ recorded with their date in `amendments.json` and `AMENDMENTS.md`, applied again on resumption,
173
+ and listed in the report.
174
+ - Documentation: tutorials (first run, writing your own rule, a fair tuning study, reading the
175
+ report), a dashboard guide, a command-line reference, an API reference generated from the
176
+ docstrings (MkDocs with mkdocstrings, `mkdocs.yml`, extra `docs`), a page on the failure-model
177
+ calibration, and a workflow that builds the site on every push and publishes it on GitHub Pages
178
+ when the repository enables it.
179
+ - Failure-model calibration against stored ns-3 / 5G-LENA results (`docs/calibration.md`): the ns-3
180
+ failures are characterised (96 % late handover commands, no effect of T310, dependence on the
181
+ time-to-trigger, the preparation delay and the traffic); a Qout filter window of about 0.12 s
182
+ fits the mean failure rate and the ranking of the 24 reference settings but fails every
183
+ held-back set, so no calibrated preset is added and the default is unchanged.
184
+ `handoverlab.verify.ns3.compare` and `scenario_for` take `overrides` for such experiments.
185
+ - Example `examples/speed_rule.py` (a rule plug-in with its contract check).
186
+ - CI: the dashboard tests and a headless start of the dashboard in one job.
187
+
188
+ ### Changed
189
+
190
+ - `RunResult` has a new field `trace`; `Engine` accepts a level name for `log_events`.
191
+ - Study files accept `events` as a level name; `true` still means `summary` and keeps the job IDs
192
+ of earlier versions.
193
+ - `GateResult` has a field `edges` (parameter and end of every grid edge) and the status `EXTEND`.
194
+ - `study_status` moved to `handoverlab.studies.status` (still importable from
195
+ `handoverlab.studies`) and now applies grid extensions and shows the time left.
196
+ - Results are unchanged: the golden fingerprints and the bh6 regression pass, and the model
197
+ version stays 1.
198
+
199
+ ## [0.3.0a1] - 2026-10-15
200
+
201
+ Third alpha release (stage 3: studies).
202
+
203
+ ### Added
204
+
205
+ - Studies (`handoverlab.studies`): a study file (YAML, TOML or JSON) with a scenario, a full
206
+ factorial of conditions, tuning and test seeds, methods with tuning grids, fixed parameters and
207
+ natural bounds, tuning settings and comparisons; every key is checked.
208
+ - Equal-budget tuning: every tuned method evaluates the same number of settings per tuning group
209
+ (a seeded random round, then refinement one and two grid steps around the best, then random
210
+ settings if the neighbourhood is used up), on tuning seeds only.
211
+ - Selection rule: lowest unnecessary-handover rate among the settings within a failure budget (a
212
+ share of the failure rate of a reference rule); settings within 5 % count as equal and are
213
+ ordered by preparation overhead, failure rate, unnecessary-handover rate and evaluation order;
214
+ if no setting meets the budget, the lowest failure rate. Objective, constraint and tie-breaks are
215
+ configurable.
216
+ - Automatic tuning gates with the policies `warn`, `block` and `off`: `grid_edge` (selected value
217
+ at the edge of its grid, natural bounds excepted), `budget_binding` (the budget constrains no
218
+ method, or the reference has too few failures), `budget_met`, `equal_budget`, `seed_overlap`
219
+ (checked before anything runs) and `flat_optimum`. A blocking gate stops the study before the
220
+ test stage (`GateError`, exit status 3) and names every failed gate.
221
+ - Paired statistics (`handoverlab.stats`): paired differences, t intervals, percentile bootstrap
222
+ intervals of the mean difference and of the ratio of means, the Wilcoxon signed-rank test (exact
223
+ null distribution with ties and zeros, or the normal approximation with tie and continuity
224
+ corrections), Holm correction, a non-inferiority check, a planning aid and decimal rounding.
225
+ - Result storage: one atomic JSON record per job (metrics, fingerprints, parameters, timing, peak
226
+ memory), `manifest.json` with versions and sessions, `study.lock.json` with the resolved study and
227
+ its hash, `plan.json`, failed-job tracebacks, tuning files with every evaluated setting and the
228
+ gate report, and the tables `runs` and `tuning` as Parquet (with pyarrow) or CSV;
229
+ `read_table` / `write_table`.
230
+ - Resumable studies with content-hashed job IDs: re-running skips finished jobs, a changed study
231
+ re-runs only the affected jobs, and stored rule runs (same scenario, seed, rule and parameters)
232
+ are reused instead of simulated again.
233
+ - Parallel jobs in worker processes (`--workers`; default: the logical CPUs minus four, half of
234
+ them below eight), one BLAS thread per process, longest jobs first, a restart of a broken pool;
235
+ results do not depend on the worker count.
236
+ - `handoverlab study STUDY` (`--workers`, `--tuning-only`, `--dry-run`, `--status`, `--report`) and
237
+ `handoverlab report DIR` (Markdown and self-contained HTML with tables and matplotlib figures, and
238
+ `summary.json`); extra `report` (matplotlib).
239
+ - `EngineBatch`: the engines of all rules of a world share one step loop; `run_world(batch=True)`
240
+ is the default and `batch=False` runs them one by one.
241
+ - Example study `examples/study_smoke.yaml`; verification check "batched engines = engines run one
242
+ by one"; tests of the statistics, the tuning, the studies and the batched engines.
243
+ - Documentation: user-guide sections on studies and paired statistics; a validation section on the
244
+ difference in failure rates to ns-3 / 5G-LENA; the performance page with the stage-3 changes.
245
+
246
+ ### Changed
247
+
248
+ - Engine speed: with many rules per world the engines need about half the time (24 rules on the
249
+ smoke scenario: 50.7 s down to 25.0 s; 8 rules: 16.9 s to 9.7 s; with 2 rules within the noise). Per-step work that
250
+ changes only at measurement instants or at events (interference weight of the serving cells,
251
+ pending handover commands) is no longer repeated every step, and the radio-link monitoring and the
252
+ load model run on arrays over all rules. Results are bit-identical: golden fingerprints and the
253
+ bh6 regression are unchanged.
254
+ - `python -m handoverlab` guards its entry point, so worker processes can be started from it.
255
+
256
+ ## [0.2.0a1] - 2026-10-15
257
+
258
+ Second alpha release (stage 2: verification in depth).
259
+
260
+ ### Added
261
+
262
+ - `handoverlab verify` command and `handoverlab.verify.run_verify()`: check groups `pathloss`
263
+ (TR 38.901 against hand values), `radio` (radio statistics against the model parameters),
264
+ `invariants`, `equivalences` and `fingerprints`; `--full`, `--only`, `--json`; `--ns3` for the
265
+ reference comparison with stored ns-3 / 5G-LENA results; `--results FILE [--sample N]` to
266
+ recompute stored runs and compare event-log fingerprints and metrics.
267
+ - Debug mode of the engine (`debug=True`, `handoverlab run --debug`, `HANDOVERLAB_DEBUG=1`) with
268
+ invariant checks at every step, validation of the values returned by rules and a read-only
269
+ observation; `InvariantError`.
270
+ - More conservation checks at the end of every run: every attempt gets exactly one outcome, and
271
+ every preparation ends as lost, cancelled, used, unused or open (also across the warm-up).
272
+ - Event-log fingerprints (`handoverlab.eventlog`): SHA-256 of a canonical CSV form, computed for
273
+ every run (`RunResult.fingerprint`, `n_events`) and stored in CSV and JSON results together with
274
+ the rule key.
275
+ - Event logs as tables: `write_events` (Parquet with `pyarrow`, else CSV; extra `parquet`),
276
+ `read_events`, typed column catalogue `EVENT_COLUMNS`; `run --events` accepts `.parquet`.
277
+ - Golden event-log fingerprints of eight short reference runs (`handoverlab/verify/data/golden.json`,
278
+ regenerated with `python -m handoverlab.verify.golden --write`).
279
+ - Contract test kit for rule plug-ins: `handoverlab.testing.check_rule` and the example plug-in
280
+ `ScaledA3`.
281
+ - Built-in scenarios with scenario cards (`smoke`, `urban-grid-10ghz`, `open-area-10ghz`,
282
+ `ns3-urban-3.5ghz`): YAML cards in the package, Markdown pages and layout drawings in
283
+ `docs/scenarios`, `handoverlab scenarios`, `builtin_scenario()`, `scenario: NAME` and
284
+ `scenario: {base: NAME, ...}` in configuration files.
285
+ - Protocol switch `cfg_loss_as_failure` (check mode) for the equivalence "CHO with immediate
286
+ execution = A3".
287
+ - Reference data of an ns-3 / 5G-LENA study (24 A3 settings, four conditions) with acceptance
288
+ limits; result of this version: median ratios 0.95 (handovers) and 1.04 (unnecessary handovers),
289
+ rank correlation 0.99 for both.
290
+ - Wider regression sample against the bh6 prototype (eight more cases with four rules each,
291
+ metrics and event logs).
292
+ - Documentation: verification and performance pages, user-guide sections on scenarios, event logs,
293
+ verification, debug mode and the contract kit.
294
+
295
+ ### Changed
296
+
297
+ - The world build is about a third faster (path loss, LOS state, random fields, line of sight);
298
+ results are bit-identical.
299
+ - The engine always keeps its event log internally to compute the fingerprint; `RunResult.events`
300
+ is still set only when requested. Event-log values are plain Python numbers.
301
+ - Result CSV files have the new columns `fingerprint` and `n_events`; JSON records also hold `key`.
302
+
303
+ ## [0.1.0a1] - 2026-10-15
304
+
305
+ First alpha release (stage 1: the core simulator).
306
+
307
+ ### Added
308
+
309
+ - `handoverlab` package (src layout, Python 3.9 or later; NumPy, SciPy, PyYAML).
310
+ - Scenarios: TR 37.885 urban street grid with a configurable number of blocks (default 3 x 3),
311
+ open area with a hexagonal site grid, explicit site lists.
312
+ - Channel: TR 38.901 UMi street canyon and UMa path loss and LOS probability; building line of
313
+ sight plus the spatially consistent LOS state; place-tied correlated shadowing with an optional
314
+ share that moves with the user; time-varying component; L1 measurement error; beam gain and
315
+ load-coupled interference.
316
+ - Mobility: street-following users with turn probabilities, random waypoints, speed profiles and
317
+ user-defined speed groups.
318
+ - Measurement chain: L1 samples, TS 38.331 L3 filter, Event A3 with time-to-trigger, periodic
319
+ reporting.
320
+ - Handover procedures: baseline handover and Rel-16 conditional handover (up to 8 prepared cells),
321
+ with a radio-link failure model (Qout/Qin, N310/N311/T310, re-establishment, CHO recovery).
322
+ - Rule interface (`BaselineRule`, `ConditionalRule`) with a registry, a decorator and the
323
+ `handoverlab.rules` entry-point group; built-in rules `a3` and `cho`.
324
+ - Outcome label for every attempt, failures by cause, preparation, interruption and service
325
+ metrics; conservation checks at the end of every run.
326
+ - Configuration as YAML, TOML, JSON or dictionary, with key-level validation messages.
327
+ - Command line `handoverlab run | rules | init`, CSV and JSON result files, event-log export.
328
+ - Tests: path loss, L3 filter, A3 and CHO triggers, failure model, configuration, command line,
329
+ determinism, and a regression test against the bh6 prototype on a 2 x 2-block grid.
330
+ - Documentation: README, user guide, contribution guide, citation file; CI on Linux, Windows and
331
+ macOS with Python 3.9-3.12.
332
+
333
+ [Unreleased]: https://github.com/mjs19971997/handoverlab/compare/v1.0.0...HEAD
334
+ [1.0.0]: https://github.com/mjs19971997/handoverlab/compare/e93b9c59e5a42665a47be91922ddd5aede998367...v1.0.0
335
+ [0.4.0a1]: https://github.com/mjs19971997/handoverlab/compare/b0802c5c18634506e47a19454ca9b51293e712d7...e93b9c59e5a42665a47be91922ddd5aede998367
336
+ [0.3.0a1]: https://github.com/mjs19971997/handoverlab/compare/a5ce5dd706c225c89c13a49fea239a89a1f56cb7...b0802c5c18634506e47a19454ca9b51293e712d7
337
+ [0.2.0a1]: https://github.com/mjs19971997/handoverlab/compare/803b849719342e96dabcf430e819d3dfcb96caf0...a5ce5dd706c225c89c13a49fea239a89a1f56cb7
338
+ [0.1.0a1]: https://github.com/mjs19971997/handoverlab/tree/803b849719342e96dabcf430e819d3dfcb96caf0
@@ -0,0 +1,32 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "HandoverLab: system-level simulation of handover and mobility beyond 5G"
4
+ abstract: >-
5
+ HandoverLab is an open-source, system-level simulator of 3GPP mobility procedures (baseline
6
+ handover with Event A3, Release-16 conditional and DAPS handover, Release-18 L1/L2-triggered
7
+ mobility) for many users in dense 5G and beyond-5G networks, on a street grid, in an open area
8
+ or along a highway. It provides a TR 38.901 received-power radio model, an L3-filtered
9
+ measurement chain, a radio-link failure model, an outcome label for every handover attempt,
10
+ a plug-in interface for handover rules, equal-budget tuning studies with paired statistics,
11
+ and verification against hand-computed values, golden event-log fingerprints and ns-3 / 5G-LENA.
12
+ version: "1.0.0"
13
+ date-released: 2026-10-16
14
+ license: MIT
15
+ repository-code: "https://github.com/mjs19971997/handoverlab"
16
+ type: software
17
+ authors:
18
+ - family-names: "Jalili Sanjarani"
19
+ given-names: "Mostafa"
20
+ - family-names: "Elmi Sola"
21
+ given-names: "Yasser"
22
+ keywords:
23
+ - handover
24
+ - conditional handover
25
+ - mobility
26
+ - mobility robustness
27
+ - radio-link failure
28
+ - system-level simulation
29
+ - 5G
30
+ - beyond 5G
31
+ - 3GPP
32
+ - Python
@@ -0,0 +1,80 @@
1
+ # Contributing to HandoverLab
2
+
3
+ Thank you for your interest. Bug reports, questions, new handover rules, scenarios and
4
+ documentation improvements are all welcome.
5
+
6
+ ## Reporting a problem
7
+
8
+ Open an issue with:
9
+
10
+ - what you ran (the configuration file or a short script) and the seed;
11
+ - what you expected and what happened (error message, or the metric that looks wrong);
12
+ - the output of `python -c "import handoverlab.io as i; print(i.provenance())"`.
13
+
14
+ ## Development set-up
15
+
16
+ ```bash
17
+ git clone https://github.com/mjs19971997/handoverlab.git
18
+ cd handoverlab
19
+ python -m venv .venv
20
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
21
+ python -m pip install -e ".[test,lint,cov]"
22
+ python -m pytest
23
+ ruff check . # the lint job of CI
24
+ python -m pytest --cov=handoverlab --cov-report=term # the coverage job (minimum: fail_under in pyproject.toml)
25
+ ```
26
+
27
+ The test suite runs in a few minutes; `handoverlab verify` adds a verification report. Keep new tests
28
+ small: a few users, tens of seconds of
29
+ simulated time, a 2 x 2-block grid or a coarse radio map.
30
+
31
+ ## Rules for code changes
32
+
33
+ - **Determinism.** Everything random must come from the streams in `handoverlab.seeds` (or, inside a
34
+ rule, from `ctx.rng`). Never use NumPy's global random state or Python's `random` module. The same
35
+ seed must give identical metrics and event logs.
36
+ - **Results change only on purpose.** If a change alters the metrics of an existing configuration,
37
+ say so in the pull request and in `CHANGELOG.md`, with the reason. The regression test against
38
+ the bh6 prototype (`tests/test_regression_bh6*.py`) must keep passing; if a model change makes it
39
+ fail deliberately, explain why and update the reference in the same pull request. The golden
40
+ event-log fingerprints (`handoverlab verify --only fingerprints`) protect every change, including
41
+ speed-ups; a change that alters them on purpose regenerates them with
42
+ `python -m handoverlab.verify.golden --write` and says why in `CHANGELOG.md`.
43
+ - **Model elements cite their source** (3GPP clause or a stated assumption) in the docstring.
44
+ - **The public surface is stable** (see `docs/stability.md`). Do not remove or rename a
45
+ configuration key, metric, rule hook or exported name; deprecate it with
46
+ `handoverlab.stability.deprecated` and keep it working until the next major release. Additions
47
+ need a new snapshot (`python -m handoverlab.stability --write tests/data/public_api.json`).
48
+ - **Coverage does not fall.** CI fails below the minimum total coverage in `pyproject.toml`
49
+ (`[tool.coverage.report] fail_under`); new code comes with tests.
50
+ - **Style.** `ruff check .` passes (rules E, F and W). Readable NumPy code, docstrings on public
51
+ functions, lines up to 120 characters, an `# SPDX-License-Identifier: MIT` line at the top of
52
+ each source file. Python 3.9 must keep working (no `match`, no `X | Y` type unions at run time).
53
+
54
+ ## Adding a handover rule
55
+
56
+ Subclass `handoverlab.rules.BaselineRule` or `ConditionalRule`, implement the hooks (see the user
57
+ guide), register it with `@register_rule("name")` or an entry point, and add a test that runs it on
58
+ a synthetic world (see `tests/helpers.py`) and on a small scenario. Add a contract test with
59
+ `handoverlab.testing.check_rule`; if the rule reduces to a built-in rule for some parameters, pass
60
+ `reduces_to` so that both are checked to give identical event logs.
61
+
62
+ ## Adding or changing a scenario
63
+
64
+ Built-in scenarios are YAML cards in `src/handoverlab/scenarios/cards`. Give the source of every
65
+ value that differs from the defaults, the limitations and the reference results, then regenerate
66
+ the documentation pages with `handoverlab scenarios --docs docs/scenarios` (a test checks that they
67
+ are current). The how-to guide `docs/guides/add-scenario.md` walks through it.
68
+
69
+ ## Releases
70
+
71
+ Releases are made by the maintainers by publishing a GitHub release; see `docs/RELEASING.md`.
72
+
73
+ ## Pull requests
74
+
75
+ - One topic per pull request, with tests.
76
+ - All CI jobs (lint; tests on Linux, Windows, macOS with Python 3.9-3.12; coverage; docs) must
77
+ pass.
78
+ - Add an entry under "Unreleased" in `CHANGELOG.md`.
79
+
80
+ By contributing you agree that your contribution is licensed under the MIT licence of the project.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mostafa Jalili Sanjarani and Yasser Elmi Sola
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.
@@ -0,0 +1,8 @@
1
+ # Source distribution: the package, its tests and the files the tests and the documentation need.
2
+ include CHANGELOG.md CITATION.cff CONTRIBUTING.md LICENSE README.md .zenodo.json mkdocs.yml
3
+ recursive-include tests *.py *.json
4
+ recursive-include examples *.py *.yaml *.toml
5
+ recursive-include tools *.py
6
+ graft docs
7
+ prune docs/fa
8
+ global-exclude *.py[cod] __pycache__