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.
- handoverlab-1.0.0/.zenodo.json +26 -0
- handoverlab-1.0.0/CHANGELOG.md +338 -0
- handoverlab-1.0.0/CITATION.cff +32 -0
- handoverlab-1.0.0/CONTRIBUTING.md +80 -0
- handoverlab-1.0.0/LICENSE +21 -0
- handoverlab-1.0.0/MANIFEST.in +8 -0
- handoverlab-1.0.0/PKG-INFO +363 -0
- handoverlab-1.0.0/README.md +315 -0
- handoverlab-1.0.0/docs/RELEASING.md +100 -0
- handoverlab-1.0.0/docs/calibration.md +111 -0
- handoverlab-1.0.0/docs/guides/add-scenario.md +83 -0
- handoverlab-1.0.0/docs/guides/daps-ltm.md +145 -0
- handoverlab-1.0.0/docs/guides/dashboard.md +63 -0
- handoverlab-1.0.0/docs/guides/export-event-logs.md +63 -0
- handoverlab-1.0.0/docs/guides/highway.md +71 -0
- handoverlab-1.0.0/docs/guides/learning-rule.md +126 -0
- handoverlab-1.0.0/docs/guides/many-cores.md +76 -0
- handoverlab-1.0.0/docs/guides/reproduce.md +55 -0
- handoverlab-1.0.0/docs/guides/upper-mid-band.md +66 -0
- handoverlab-1.0.0/docs/images/dashboard-charts.png +0 -0
- handoverlab-1.0.0/docs/images/dashboard.png +0 -0
- handoverlab-1.0.0/docs/images/report-comparison.png +0 -0
- handoverlab-1.0.0/docs/images/report-tuning.png +0 -0
- handoverlab-1.0.0/docs/index.md +51 -0
- handoverlab-1.0.0/docs/performance.md +119 -0
- handoverlab-1.0.0/docs/reference/api/config.md +8 -0
- handoverlab-1.0.0/docs/reference/api/engine.md +23 -0
- handoverlab-1.0.0/docs/reference/api/eventlog.md +8 -0
- handoverlab-1.0.0/docs/reference/api/gui.md +13 -0
- handoverlab-1.0.0/docs/reference/api/index.md +8 -0
- handoverlab-1.0.0/docs/reference/api/rules.md +18 -0
- handoverlab-1.0.0/docs/reference/api/scenarios.md +8 -0
- handoverlab-1.0.0/docs/reference/api/simulate.md +13 -0
- handoverlab-1.0.0/docs/reference/api/stability.md +9 -0
- handoverlab-1.0.0/docs/reference/api/stats.md +8 -0
- handoverlab-1.0.0/docs/reference/api/studies.md +43 -0
- handoverlab-1.0.0/docs/reference/api/testing.md +8 -0
- handoverlab-1.0.0/docs/reference/api/verify.md +18 -0
- handoverlab-1.0.0/docs/reference/cli.md +171 -0
- handoverlab-1.0.0/docs/reference/metrics.md +60 -0
- handoverlab-1.0.0/docs/scenarios/fr3-urban-15ghz.md +91 -0
- handoverlab-1.0.0/docs/scenarios/fr3-urban-15ghz.svg +89 -0
- handoverlab-1.0.0/docs/scenarios/highway-10ghz.md +95 -0
- handoverlab-1.0.0/docs/scenarios/highway-10ghz.svg +10 -0
- handoverlab-1.0.0/docs/scenarios/ns3-urban-3.5ghz.md +91 -0
- handoverlab-1.0.0/docs/scenarios/ns3-urban-3.5ghz.svg +15 -0
- handoverlab-1.0.0/docs/scenarios/open-area-10ghz.md +90 -0
- handoverlab-1.0.0/docs/scenarios/open-area-10ghz.svg +126 -0
- handoverlab-1.0.0/docs/scenarios/smoke.md +91 -0
- handoverlab-1.0.0/docs/scenarios/smoke.svg +34 -0
- handoverlab-1.0.0/docs/scenarios/urban-grid-10ghz.md +92 -0
- handoverlab-1.0.0/docs/scenarios/urban-grid-10ghz.svg +89 -0
- handoverlab-1.0.0/docs/stability.md +79 -0
- handoverlab-1.0.0/docs/tutorials/first-run.md +128 -0
- handoverlab-1.0.0/docs/tutorials/own-rule.md +112 -0
- handoverlab-1.0.0/docs/tutorials/reading-the-report.md +116 -0
- handoverlab-1.0.0/docs/tutorials/tuning-study.md +176 -0
- handoverlab-1.0.0/docs/user_guide.md +707 -0
- handoverlab-1.0.0/docs/verification.md +162 -0
- handoverlab-1.0.0/examples/compare_a3_cho.py +19 -0
- handoverlab-1.0.0/examples/open_area.toml +37 -0
- handoverlab-1.0.0/examples/smoke.yaml +12 -0
- handoverlab-1.0.0/examples/speed_rule.py +47 -0
- handoverlab-1.0.0/examples/street_grid.yaml +24 -0
- handoverlab-1.0.0/examples/study_smoke.yaml +57 -0
- handoverlab-1.0.0/mkdocs.yml +88 -0
- handoverlab-1.0.0/pyproject.toml +88 -0
- handoverlab-1.0.0/setup.cfg +4 -0
- handoverlab-1.0.0/src/handoverlab/__init__.py +30 -0
- handoverlab-1.0.0/src/handoverlab/__main__.py +7 -0
- handoverlab-1.0.0/src/handoverlab/channel/__init__.py +8 -0
- handoverlab-1.0.0/src/handoverlab/channel/pathloss.py +122 -0
- handoverlab-1.0.0/src/handoverlab/channel/radiomap.py +198 -0
- handoverlab-1.0.0/src/handoverlab/channel/source.py +130 -0
- handoverlab-1.0.0/src/handoverlab/cli.py +334 -0
- handoverlab-1.0.0/src/handoverlab/config.py +448 -0
- handoverlab-1.0.0/src/handoverlab/engine/__init__.py +10 -0
- handoverlab-1.0.0/src/handoverlab/engine/batch.py +200 -0
- handoverlab-1.0.0/src/handoverlab/engine/core.py +546 -0
- handoverlab-1.0.0/src/handoverlab/engine/invariants.py +141 -0
- handoverlab-1.0.0/src/handoverlab/engine/metrics.py +107 -0
- handoverlab-1.0.0/src/handoverlab/engine/procedures.py +452 -0
- handoverlab-1.0.0/src/handoverlab/eventlog.py +335 -0
- handoverlab-1.0.0/src/handoverlab/gui/__init__.py +53 -0
- handoverlab-1.0.0/src/handoverlab/gui/app.py +139 -0
- handoverlab-1.0.0/src/handoverlab/gui/logic.py +466 -0
- handoverlab-1.0.0/src/handoverlab/io.py +79 -0
- handoverlab-1.0.0/src/handoverlab/layout.py +214 -0
- handoverlab-1.0.0/src/handoverlab/measurement.py +115 -0
- handoverlab-1.0.0/src/handoverlab/mobility.py +169 -0
- handoverlab-1.0.0/src/handoverlab/py.typed +0 -0
- handoverlab-1.0.0/src/handoverlab/rules/__init__.py +107 -0
- handoverlab-1.0.0/src/handoverlab/rules/base.py +249 -0
- handoverlab-1.0.0/src/handoverlab/rules/builtin.py +278 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/__init__.py +247 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/cards/fr3-urban-15ghz.yaml +33 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/cards/highway-10ghz.yaml +39 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/cards/ns3-urban-3.5ghz.yaml +52 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/cards/open-area-10ghz.yaml +28 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/cards/smoke.yaml +31 -0
- handoverlab-1.0.0/src/handoverlab/scenarios/cards/urban-grid-10ghz.yaml +27 -0
- handoverlab-1.0.0/src/handoverlab/seeds.py +40 -0
- handoverlab-1.0.0/src/handoverlab/simulate.py +141 -0
- handoverlab-1.0.0/src/handoverlab/stability.py +186 -0
- handoverlab-1.0.0/src/handoverlab/stats.py +249 -0
- handoverlab-1.0.0/src/handoverlab/studies/__init__.py +33 -0
- handoverlab-1.0.0/src/handoverlab/studies/config.py +464 -0
- handoverlab-1.0.0/src/handoverlab/studies/report.py +538 -0
- handoverlab-1.0.0/src/handoverlab/studies/runner.py +307 -0
- handoverlab-1.0.0/src/handoverlab/studies/status.py +223 -0
- handoverlab-1.0.0/src/handoverlab/studies/store.py +251 -0
- handoverlab-1.0.0/src/handoverlab/studies/study.py +559 -0
- handoverlab-1.0.0/src/handoverlab/studies/tuning.py +337 -0
- handoverlab-1.0.0/src/handoverlab/testing.py +171 -0
- handoverlab-1.0.0/src/handoverlab/traffic.py +30 -0
- handoverlab-1.0.0/src/handoverlab/verify/__init__.py +74 -0
- handoverlab-1.0.0/src/handoverlab/verify/data/golden.json +114 -0
- handoverlab-1.0.0/src/handoverlab/verify/data/ns3_urban_a3.json +1471 -0
- handoverlab-1.0.0/src/handoverlab/verify/engine_checks.py +105 -0
- handoverlab-1.0.0/src/handoverlab/verify/golden.py +126 -0
- handoverlab-1.0.0/src/handoverlab/verify/ns3.py +125 -0
- handoverlab-1.0.0/src/handoverlab/verify/radio.py +190 -0
- handoverlab-1.0.0/src/handoverlab/verify/report.py +74 -0
- handoverlab-1.0.0/src/handoverlab/verify/stored.py +70 -0
- handoverlab-1.0.0/src/handoverlab/world.py +55 -0
- handoverlab-1.0.0/src/handoverlab.egg-info/PKG-INFO +363 -0
- handoverlab-1.0.0/src/handoverlab.egg-info/SOURCES.txt +161 -0
- handoverlab-1.0.0/src/handoverlab.egg-info/dependency_links.txt +1 -0
- handoverlab-1.0.0/src/handoverlab.egg-info/entry_points.txt +2 -0
- handoverlab-1.0.0/src/handoverlab.egg-info/requires.txt +30 -0
- handoverlab-1.0.0/src/handoverlab.egg-info/top_level.txt +1 -0
- handoverlab-1.0.0/tests/conftest.py +15 -0
- handoverlab-1.0.0/tests/data/bh6_reference.json +325 -0
- handoverlab-1.0.0/tests/data/bh6_wide.json +2373 -0
- handoverlab-1.0.0/tests/data/make_bh6_reference.py +61 -0
- handoverlab-1.0.0/tests/data/make_bh6_wide.py +100 -0
- handoverlab-1.0.0/tests/data/public_api.json +513 -0
- handoverlab-1.0.0/tests/helpers.py +39 -0
- handoverlab-1.0.0/tests/test_batch.py +45 -0
- handoverlab-1.0.0/tests/test_config_cli.py +116 -0
- handoverlab-1.0.0/tests/test_contract.py +75 -0
- handoverlab-1.0.0/tests/test_determinism.py +46 -0
- handoverlab-1.0.0/tests/test_engine_triggers.py +108 -0
- handoverlab-1.0.0/tests/test_eventlog.py +92 -0
- handoverlab-1.0.0/tests/test_gui.py +140 -0
- handoverlab-1.0.0/tests/test_highway.py +99 -0
- handoverlab-1.0.0/tests/test_invariants.py +87 -0
- handoverlab-1.0.0/tests/test_levels_status_extend.py +196 -0
- handoverlab-1.0.0/tests/test_measurement.py +130 -0
- handoverlab-1.0.0/tests/test_pathloss.py +55 -0
- handoverlab-1.0.0/tests/test_regression_bh6.py +101 -0
- handoverlab-1.0.0/tests/test_regression_bh6_wide.py +85 -0
- handoverlab-1.0.0/tests/test_rules_rc2.py +212 -0
- handoverlab-1.0.0/tests/test_scenarios.py +71 -0
- handoverlab-1.0.0/tests/test_stability.py +124 -0
- handoverlab-1.0.0/tests/test_stats.py +131 -0
- handoverlab-1.0.0/tests/test_study.py +224 -0
- handoverlab-1.0.0/tests/test_tools.py +47 -0
- handoverlab-1.0.0/tests/test_tuning.py +128 -0
- handoverlab-1.0.0/tests/test_verify.py +88 -0
- handoverlab-1.0.0/tools/compare_fingerprints.py +131 -0
- handoverlab-1.0.0/tools/mkdocs_hooks.py +26 -0
- 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__
|