Top-level package¶
Everything most scripts need is importable from handoverlab directly.
handoverlab
¶
HandoverLab: system-level simulation of handover and mobility procedures.
Quick start::
import handoverlab as hl
sc = hl.Scenario() # 3 x 3-block street grid, 10 GHz
res = hl.simulate(sc, [{"rule": "a3"}, {"rule": "cho"}], seeds=[1])
for r in res:
print(r.rule, r.metrics["handovers_pm"], r.metrics["failures_pm"])
ConfigError
¶
Bases: ValueError
Invalid configuration; the message names the offending key.
LayoutConfig
dataclass
¶
Deployment geometry.
street_grid is the urban grid of 3GPP TR 37.885 Annex A: blocks of
pitch_m (433 m x 250 m between street centre lines), streets
street_width_m wide. The default of 3 x 3 blocks is the minimum area
of TR 37.885 Table A-1. Sites stand on the pavement every
site_spacing_m metres, site_offset_m from the street centre line.
open_area is the same rectangle without buildings, with sites on a
hexagonal grid of spacing site_spacing_m.
highway (since 1.0.0rc3) is the straight multi-lane road of the
TR 37.885 highway case: lanes lanes of lane_width_m per direction
over road_length_m, without buildings; roadside sites every
site_spacing_m metres, site_offset_m beyond the road edge,
alternately on the two sides.
indoor (since 1.7.0) is the indoor office of TR 38.901 Table 7.2-2:
a hall of 120 m x 50 m without walls and ceiling sites on a rectangular
grid of spacing site_spacing_m (20 m in the standard).
area_m (since 1.7.0) sets the width and height of the rectangle of
open_area and indoor [m]; unset, open_area takes the size of
the street grid and indoor 120 m x 50 m. min_site_distance_m
(since 1.7.0) is the minimum 2D distance of a user's start and waypoints
from every site in open_area and indoor (TR 38.901 Table 7.2-3,
RMa: 35 m); the straight legs between them may pass closer.
wrap (since 1.11.0, highway only) applies the wrap-around of TR 37.885
Table A-1: a vehicle keeps its lane and direction and re-enters the road
at the other end, and every distance and angle between a user and a site
is taken to the nearest image of the site along the road (period
road_length_m); the place-tied shadowing field is periodic along the
road. Without it (the default) a vehicle turns at the end of the road.
sites_file (since 1.0.0rc3) names a CSV file with the columns x and
y [m]; it is read when the scenario is loaded and fills sites.
size_m: Tuple[float, float]
property
¶
Width and height of the simulated rectangle [m].
MobilityConfig
dataclass
¶
Number of users and their speeds; groups overrides the named profile.
ProtocolConfig
dataclass
¶
Measurement chain, handover timing and the failure model.
Defaults follow 3GPP TR 36.839 / TR 36.842 (Qout -8 dB, Qin -6 dB, T310 1 s, N310 = N311 = 1, preparation 50 ms, execution 40 ms) and the L3 filter of TS 38.331 clause 5.5.3.2.
RadioConfig
dataclass
¶
Carrier, link budget, propagation and the antenna abstraction (TR 38.901).
beams (since 1.10.0) replaces the antenna abstraction by sectors with
SSB beams (BeamConfig); layers (since 1.10.0) adds carriers
with their own cells (LayerConfig). With either of them the
measurements are SS-RSRP per resource element (the power over the
bandwidth minus 10 log10(bandwidth / scs_khz)); scs_khz unset is
30 kHz below 7.125 GHz and 120 kHz above.
channel_detail (since 2.1.0) selects how closely the large-scale channel follows TR 38.901 V19.5.0:
"3gpp"(the default): the place-tied shadowing and LOS fields have the exponential autocorrelation exp(-d / d_cor) of Eq. (7.4-5) (clause 7.4.4; clause 7.6.3.1 for the LOS state), the shadowing of different sites is uncorrelated unlessshadow_site_corris set (clause 7.5, step 4), the UMa LOS probability has the factor C'(h_UT) of Table 7.4.2-1 and the UMa path loss the random effective environment height h_E of Table 7.4.1-1 note 1 (both matter only for h_UT >= 13 m), a co-sited layer must keep the LOS-state and shadowing settings of the first layer (clause 7.6.5), the antenna heights must lie in the applicability ranges of Table 7.4.1-1, and an NTN satellite takes the tabled 3 dB beamwidth of TR 38.821 Tables 6.1.1.1-1/-2 (Table 6.1.1.1-4);"simple": the model of 2.0.0 and 1.x (Gaussian-shaped field correlation exp(-(d / d_cor)^2), shadowing 0.5-correlated between sites, no C'(h_UT), h_E = 1 m, no checks of the heights or of co-sited layers, the beamwidth computed from the aperture).
shadow_site_corr unset (None, since 2.1.0) is 0 with "3gpp" and 0.5 with "simple".
RunConfig
dataclass
¶
Length of a run and (since 1.11.0) the backend of the step loop.
backend "numpy" (the default and the reference) or "numba": a compiled kernel for the
radio-link monitoring of every step (handoverlab.engine._kernels; needs pip install
handoverlab[fast]). Both give bit-identical results, so the backend is not part of the job IDs.
Scenario
dataclass
¶
One simulated condition. Together with a seed it fixes a world.
to_dict(full: bool = False) -> Dict[str, Any]
¶
The scenario as a plain dictionary.
Keys added after 1.0.0rc2 (LATE_KEYS) are left out while they
have their default value, so that the job IDs and study locks of
scenarios that do not use them stay as they were; full=True keeps
every key.
from_dict(data: Union[Dict[str, Any], str]) -> 'Scenario'
classmethod
¶
Scenario from a dictionary.
data may also be the name of a built-in scenario, or contain the key
base with such a name; the other values then change the built-in
scenario (see handoverlab.scenarios).
with_overrides(overrides: Dict[str, Any]) -> 'Scenario'
¶
Copy with changed values; keys are dotted paths such as "layout.site_spacing_m".
validate() -> None
¶
Range checks that name the offending key.
Service
dataclass
¶
A service class (5QI of TS 23.501 Table 5.7.4-1) and its label thresholds.
SpeedGroup
dataclass
¶
A share of users with a uniform speed range and lateral displacement range.
TrafficConfig
dataclass
¶
Services, background load and the scheduler abstraction.
InvariantError
¶
Bases: RuntimeError
An internal consistency check of the engine failed.
A3
¶
Bases: BaselineRule
Event A3 with one offset and one time-to-trigger for all users.
off_db A3 offset [dB] (neighbour better than serving by more than this) ttt_s time-to-trigger [s]
CHO
¶
Bases: ConditionalRule
Conditional handover with fixed offsets.
o_prep_db preparation offset [dB]: a neighbour is reported and prepared
when it is better than the serving cell by this much for
ttt_prep_s (negative: prepared before it is stronger)
o_exec_db execution offset [dB] carried in the conditional configuration
ttt_exec_s time-to-trigger of the execution condition [s]
ttt_prep_s time-to-trigger of the reporting condition [s]
n_max largest prepared set (1-8, maxNrofCondCells-r16)
rel_hys_db release hysteresis [dB]
immediate execute as soon as the configuration arrives
CLTM
¶
Bases: CLTMRule
Conditional L1/L2-triggered mobility with fixed offsets (3GPP Rel-19 work on LTM).
Specification: TS 38.300 clause 9.2.3.5 (LTM, Rel-18) for the candidate configuration, L1
measurements and RACH-less execution; conditional LTM is TS 38.300 V19.4.0 clause 9.2.3.7: the
UE executes a configured LTM cell switch when L1-based or L3-based execution conditions are met,
without the cell switch command. The rule was written before that text was frozen; it models an
L1-based condition like the LTM switch condition: a configured
candidate whose unfiltered L1 measurement is better than the serving cell by o_switch_db
at every L1 sample for ttt_switch_s; the UE then executes at once (exec_s).
o_prep_db candidate preparation offset [dB] (L3)
o_switch_db cell switch offset [dB] (L1), evaluated by the UE
ttt_switch_s time the switch condition must hold [s] (0: one L1 sample)
ttt_prep_s time-to-trigger of the candidate report [s]
n_max largest candidate set (1-8)
rel_hys_db release hysteresis [dB]
exec_s execution time of the cell switch [s]
recovery a failed UE that selects a candidate recovers into it in cho_recovery_s
DAPS
¶
Bases: DAPSRule
Dual active protocol stack (DAPS) handover triggered by Event A3 (3GPP Rel-16).
Specification: TS 38.300 clause 9.2.3.2 (handover; DAPS keeps the source link during the handover), clause 9.2.7 (radio-link failure detection on the source continues until the target is joined), TS 38.331 clause 5.3.5.8.3 (failure of the reconfiguration with sync: fallback to the source when its link has not failed).
Trigger: Event A3 (TS 38.331 clause 5.5.4.4), neighbour better than the
serving cell by off_db for ttt_s; report, preparation and command
as a baseline handover.
off_db A3 offset [dB] ttt_s time-to-trigger [s] interruption_s user-plane interruption of an execution [s] (Rel-16 aims at 0) fallback fall back to the source if the target cannot be joined and the source SINR is at least Qout source_data the source keeps serving data during the execution
With fallback=False and source_data=False the rule gives the same
event log as A3 with the same offset and time-to-trigger.
LTM
¶
Bases: LTMRule
L1/L2-triggered mobility (LTM) with fixed offsets (3GPP Rel-18).
Specification: TS 38.300 clause 9.2.3.5 (LTM: candidate cell configuration, L1 measurement reporting, cell switch command, early timing advance acquisition and RACH-less execution); TS 38.321 (LTM cell switch command MAC CE); TS 38.331 clause 5.5.3.2 (L3 filtering of the measurements that select the candidates).
Candidates: a neighbour whose L3-filtered measurement is better than the
serving cell by o_prep_db for ttt_prep_s is reported and
configured as a candidate (at most n_max, best first; released
rel_hys_db below the offset). Cell switch: the network switches to
a configured candidate whose unfiltered L1 measurement is better than the
serving cell by o_switch_db at every L1 report for ttt_switch_s;
the command reaches the UE after cmd_delay_s, the execution takes
exec_s.
o_prep_db candidate preparation offset [dB] (L3)
o_switch_db cell switch offset [dB] (L1)
ttt_switch_s time the switch condition must hold [s] (0: one L1 report)
ttt_prep_s time-to-trigger of the candidate report [s]
n_max largest candidate set (1-8)
rel_hys_db release hysteresis [dB]
cmd_delay_s delay of the cell switch command [s]
exec_s execution time of the cell switch [s]
recovery a failed UE that selects a candidate recovers into it in cho_recovery_s
BaselineRule
¶
Bases: Rule
Baseline handover triggered by Event A3.
a3_offsets(obs: Observation) -> Union[float, np.ndarray]
¶
A3 offset [dB]: a scalar or an array (U, M).
time_to_trigger(obs: Observation) -> Union[float, np.ndarray]
¶
Time-to-trigger [s]: a scalar, an array (U,) or (since 1.10.0) an array (U, M).
entering(obs: Observation, offsets) -> Optional[tuple]
¶
Own entering condition (since 1.10.0): (condition, score), both (U, M), or None for Event A3.
condition says where the event's entering condition holds (the serving cell and users that are
not connected are excluded by the engine); among the cells whose time-to-trigger expires, the one with
the highest score becomes the target. The default (None) is Event A3 with offsets.
CLTMRule
¶
Bases: LTMRule
Conditional L1/L2-triggered mobility (Rel-19 work on LTM; available since version 1.6.0).
As LTMRule (candidates configured on L3 reports, cell switch on L1 measurements,
RACH-less execution), but the UE evaluates the switch condition itself and executes as soon as
it holds, without a cell switch command (as conditional handover does on L3 measurements).
cmd_delay_s is not used. The same hooks as LTMRule.
ConditionalRule
¶
Bases: Rule
Conditional handover (Rel-16).
Attributes read by the engine (set them in __init__):
report_offset_db the UE reports a neighbour that is this much better
than the serving cell for ttt_prep_s
ttt_prep_s time-to-trigger of the reporting condition
ttt_exec_s time-to-trigger of the execution condition
n_max largest prepared set (at most 8)
release_hys_db a prepared cell is released when it falls this far
below its preparation offset
stick_db ranking bonus of an already prepared cell
immediate execute as soon as the configuration arrives (the
conditional handover then acts as a baseline one)
prep_offsets(obs: Observation) -> Union[float, np.ndarray]
¶
Preparation offset [dB]: a scalar or an array (U, M).
exec_offsets(obs: Observation) -> Union[float, np.ndarray]
¶
Execution offset [dB] written into a new conditional configuration: scalar or (U, M).
exec_condition(obs: Observation) -> Optional[np.ndarray]
¶
A further execution condition (U, M) that must hold together with the execution offset (since 2.0.0), e.g. condEventD1 / D2 / T1 of TS 38.331 5.5.4.15-16; None (the default): the offset alone. Called at every measurement instant (since 2.1.0), so that a condition with a state stays current.
release_user(u: int) -> None
¶
The conditional configurations of user u were removed (after an execution or a failure; since
2.1.0): reset any state kept for them. The default does nothing.
DAPSRule
¶
Bases: BaselineRule
Dual active protocol stack (DAPS) handover (Rel-16, TS 38.300 clause 9.2.3.2).
Triggered like a baseline handover (hooks BaselineRule.a3_offsets
and BaselineRule.time_to_trigger). Attributes read by the engine:
interruption_s user-plane interruption of an execution while the
source link carries data (Rel-16 aims at 0 ms)
fallback an execution that cannot join the target falls back to
the source if the source SINR is at least Qout (TS 38.331
clause 5.3.5.8.3) instead of a re-establishment
source_data the source keeps serving data during the execution
LTMRule
¶
Bases: Rule
L1/L2-triggered mobility (Rel-18, TS 38.300 clause 9.2.3.5).
Attributes read by the engine (set them in __init__):
report_offset_db L3 report of a neighbour this much better than serving
for ttt_prep_s (candidate configuration)
ttt_prep_s time-to-trigger of the L3 reporting condition
n_max largest set of candidate cells (at most 8)
release_hys_db a candidate is released when it falls this far below its
preparation offset
stick_db ranking bonus of an already configured candidate
ttt_switch_s the L1 switch condition must hold this long
cmd_delay_s delay of the cell switch command (MAC CE)
exec_s execution time of the cell switch (RACH-less, early TA)
recovery a failed UE that selects a candidate recovers into it in
cho_recovery_s (otherwise a full re-establishment)
Rule
¶
Common part of all rules.
params() -> Dict[str, Any]
¶
Parameters of the rule (written into the results).
reset(ctx: RuleContext) -> None
¶
Called once before a run.
memory_key(obs: Observation) -> Optional[np.ndarray]
¶
Key (U,) under which an attempt prepared now is remembered, or None.
on_outcome(rec: OutcomeRecord) -> None
¶
Outcome of a handover attempt (only called if learns).
on_link_failure(rec: FailureRecord) -> None
¶
Radio-link failure without a handover (only called if learns).
RunResult
dataclass
¶
Metrics of one rule on one seed.
fingerprint is the SHA-256 of the canonical event log (see
handoverlab.eventlog) and is always set; events holds the log
itself only if it was requested. key is the registry key of the rule
("a3", "cho", ...), so that the run can be repeated. trace
holds the procedure trace at event-log level "full" (see
handoverlab.eventlog).
World
dataclass
¶
n_cells: int
property
¶
Number of cells: one per site, or the cells of the extended radio model.
cell_sites: np.ndarray
property
¶
(M, 2) positions of the cells' sites [m].
nominal_cell
property
¶
Nominal best server at points (the operator's plan, see RadioMap).
cell_center(t: float) -> np.ndarray
¶
(M, 2) reference locations of the cells at time t [m]: the sites, or the moving beam centres (NTN).
load_config(source: Union[str, os.PathLike, Dict[str, Any]]) -> Experiment
¶
Experiment from a file path or a dictionary.
fingerprint(events: Iterable) -> str
¶
SHA-256 (hex) of the canonical CSV of an event log.
read_events(path: str) -> List[dict]
¶
Rows of an event table written by write_events (typed values, in file order).
write_events(results: Sequence, path: str) -> str
¶
Write the event logs of results to path and return the path written.
.parquet writes Parquet if pyarrow is installed; without it the
table is written as CSV next to it (.csv instead of .parquet) and a
warning is issued. Any other name writes CSV.
make_rule(spec: Dict[str, Any]) -> Rule
¶
Rule from {"rule": <key>, <parameters>...}.
register_rule(key: str)
¶
Class decorator that makes a rule available under key.
builtin_scenario(name: str) -> Scenario
¶
The built-in scenario name.
scenario_card(name: str) -> Dict[str, Any]
¶
The scenario card of name as a dictionary (scenario holds the values that differ from the defaults).
scenario_names(deprecated: bool = False) -> List[str]
¶
Names of the built-in scenarios; deprecated ones (DEPRECATED) only with deprecated=True.
run_experiment(exp: Experiment, log_events: Union[bool, str] = False, progress: Optional[Callable[[str], None]] = None, debug: Optional[bool] = None) -> List[RunResult]
¶
Run an Experiment (scenario, seeds and rules).
run_world(world: World, rules: Sequence[RuleSpec], log_events: Union[bool, str] = False, chunk_steps: int = 1000, debug: Optional[bool] = None, batch: bool = True, progress: Optional[Callable[[float], None]] = None) -> List[RunResult]
¶
Run every rule of rules on world with identical radio samples.
log_events is the event-log level: "off" (or False), "summary"
(or True: RunResult.events) or "full" (also
RunResult.trace). progress is called after every radio chunk
with the share of the run simulated so far (0 to 1).
debug turns on the invariant checks of the engine (see
handoverlab.engine.invariants); results are the same either way.
With batch (the default) the engines of all rules share one step loop
(EngineBatch); results are
bit-identical to running them one by one (batch=False), only faster.
Engines in debug mode always run one by one.
build_world(scenario: Scenario, seed: int) -> World
¶
Layout, radio map, trajectories, services and background load of one seed.