Engine and metrics¶
The protocol engine, the procedures and the metric catalogue.
handoverlab.engine.core
¶
Protocol engine: time loop, radio-link monitoring, attempts and outcome labels.
One Engine simulates one handover rule on given radio samples. All
users advance together on the step grid (10 ms by default); cell load and
interference are coupled through the users' serving cells. The engine draws
no random numbers, so two rules run on the same samples differ only through
their decisions.
Per step
1. end of an execution (join the target if its SINR is at least Qout,
otherwise an execution failure) or of a re-establishment;
2. radio-link monitoring: serving SINR smoothed over the Qout / Qin
windows; N310 out-of-sync indications start T310, N311 in-sync
indications stop it; expiry is a radio-link failure followed by
re-establishment in the strongest cell;
3. procedure events (a handover command or a conditional configuration
reaches the UE);
4. at measurement instants: L3 filter, outcome-label sampling, the
handover procedure (see handoverlab.engine.procedures), load and
delivered rate.
Outcome of a handover attempt (exactly one per attempt) too_late command or configuration lost, or link failure while the handover was in preparation too_early execution failed, or link failure in the target before the evaluation window closed, and the UE re-established in the source or the target wrong_cell the same, but the UE re-established in a third cell unnecessary completed; the source would not have failed during the window, and the stay was shorter than T_min of the service or the mean SINR gain over the window was below theta_Q useful completed and not unnecessary censored the run ended before the outcome was known
A link failure with no handover in flight is not an attempt; it counts as a
failure ("rlf_without_handover" if the UE re-establishes in another cell,
"coverage_rlf" if in the same cell). The SINR of the label uses the true
received powers with all cells transmitting (label_load), so the label
does not depend on what a rule does to the load.
Engine
¶
Simulation of one rule. Feed the radio chunks in order, then call finish.
log_events is the event-log level (see handoverlab.eventlog):
"off" (or False), "summary" (or True) keeps the event log for
events, "full" also keeps the procedure trace for
trace; the fingerprint is always available. debug turns on
the invariant checks of handoverlab.engine.invariants (default:
the environment variable HANDOVERLAB_DEBUG). Neither changes a
result.
meas: np.ndarray
property
¶
L3-filtered RSRP (U, M) [dBm].
events: Optional[List[dict]]
property
¶
Event log as a list of dictionaries (if the engine was created with log_events).
trace: Optional[List[dict]]
property
¶
Procedure trace as a list of dictionaries (event-log level full only).
fingerprint: str
property
¶
SHA-256 of the canonical CSV of the event log (see handoverlab.eventlog).
trace_event(t, u, event, src, cell, value=np.nan, note='')
¶
Add a row to the procedure trace (only at event-log level full).
new_flight(u, tgt, t, key=-1, npr=0, oe=np.nan)
¶
A handover attempt of user u towards tgt starts at t.
start_exec(u, tgt, t)
¶
The UE leaves the source cell and synchronises to tgt.
msg(t, n=1) -> None
¶
Count n RRC messages sent at t (since 2.1.0): MeasurementReport, RRCReconfiguration (one per
user and instant, whatever the number of conditional candidates it carries) and its
RRCReconfigurationComplete; an LTM cell switch command is a MAC CE (TS 38.300 9.2.3.5.2), not counted.
fail(u, t, kind)
¶
Mobility failure of user u: "rlf" (T310 expired), "cmd" (command lost) or "exec".
advance(ch) -> None
¶
Simulate the steps of one radio chunk.
finish() -> Dict[str, float]
¶
Close what is still open, check the invariants and return the metrics of the run.
rlm_indications(P, qo, qi, qout, qin, run, n_o, n_i, mon, t, rlm_t0)
¶
Out-of-sync / in-sync indications of one indication instant as TS 38.331 and TS 38.133 specify (since 2.1.0).
An out-of-sync indication is sent when the filtered quality is below Qout, an in-sync indication when it is
above Qin (TS 38.133 8.1.6), at most every max(10 ms, T_SSB). TS 38.331 clause 7.2: N310 counts the
out-of-sync indications while T310 is stopped and is reset by an in-sync indication; N311 counts the in-sync
indications while T310 runs and is reset by an out-of-sync indication; an instant with neither changes no
counter (5.3.10.2, NOTE 2). T310 starts when N310 is reached (5.3.10.1) and stops when N311 is reached
(5.3.10.2). mon are the users whose link is monitored, rlm_t0 the end of the monitoring restart on a
new cell. Updates run, n_o, n_i in place (1-D or 2-D arrays) and returns the users whose T310
starts.
rlm_kernel(engine)
¶
The compiled step kernel if run.backend is "numba" and it covers the engine's monitoring, else None.
It covers N310 = N311 = 1 without the monitoring load option (protocol.lm_load); other settings and
debug mode use the NumPy path.
handoverlab.engine.procedures
¶
Handover procedures: baseline, conditional, DAPS handover and L1/L2-triggered mobility.
A procedure holds the per-user state of one handover family and implements
the protocol around the decisions of a rule. The engine calls it through a
small interface (Procedure), so further procedures (for example
L1/L2-triggered mobility) can be added without touching the engine.
Baseline handover (TR 36.839 failure model)
Event A3 (offset per user and neighbour) held for the time-to-trigger ->
measurement report -> preparation (prep_s) -> handover command. A
command that arrives while T310 runs is received once the link reaches Qout
(protocol.cmd_during_t310: receive, the default since 2.0.0; with
lost it is lost, "too late"). The UE leaves the source, is interrupted
for exec_s and joins the target when its SINR is at least Qout while
T304 runs.
Conditional handover (TS 38.300 clause 9.2.3.4)
The UE reports a neighbour once it passes the reporting offset (first
report at once, then every report_interval_s). At a report the
network prepares the neighbours whose advantage reaches the preparation
offset, at most n_max (best first), and releases prepared cells that
fell release_hys_db below it. The conditional configuration reaches
the UE after prep_s (while T310 runs, as a command). The UE evaluates
the execution condition of every prepared cell at each measurement and
executes on its own when the time-to-trigger expires (since 2.1.0 also
between two measurement instants). After an execution the other prepared
cells are released; after a failure the UE recovers into a prepared cell
it selects (cho_recovery_s; since 2.1.0 with T304, TS 38.331 5.3.7.3).
DAPS handover (TS 38.300 clause 9.2.3.2, TS 38.331 clause 5.3.5.8.3)
Triggered and prepared as a baseline handover. During the execution the
source link keeps carrying data (the user-plane interruption is
interruption_s of the rule instead of exec_s) and is monitored
(since 2.1.0: T310 is not stopped, a source RLF during T304 releases it).
If the target cannot be joined before T304 expires and no source RLF was
detected, the UE falls back to the source: the attempt ends as
too_early with the cause daps_fallback and no re-establishment
follows. Not from FR2 to FR2 and not for NTN (TS 38.300).
L1/L2-triggered mobility (TS 38.300 clause 9.2.3.5)
Candidate cells are configured as the prepared cells of a conditional
handover (L3 reports, preparation offset, n_max, release). The
network evaluates the unfiltered L1 measurements: when a configured
candidate is better than the serving cell by the switch offset for
ttt_switch_s, it sends a cell switch command (MAC CE), which reaches
the UE after cmd_delay_s (while T310 runs, as a baseline command).
The execution takes exec_s of the rule (RACH-less, with early timing
advance acquisition; 20 ms more between FR1 and FR2 since 2.1.0). The
other candidates stay configured after a switch (TS 38.331 5.3.5.18.6).
Procedure
¶
Interface between the engine and a handover family.
exec_time(u) -> float
¶
Duration of an execution of user u [s].
exec_interruption(u, duration) -> float
¶
User-plane interruption of an execution of the given duration [s].
step(t)
¶
Called every step after radio-link monitoring (deliver commands or configurations).
measure(obs, counted)
¶
Called at every measurement instant with the filtered measurements.
flight_info(u, tgt)
¶
(memory key, prepared-set size, execution offset) of an attempt that starts at execution.
cancel_command(u)
¶
A pending handover command of user u is void.
on_attach(u)
¶
User u is connected to a new serving cell.
on_failure(u)
¶
User u had a failure and starts re-establishment.
clear(u, used_cell, t)
¶
Drop all preparations of user u (used_cell is the cell it went to, or -1).
after_execution(u, tgt, t)
¶
A successful execution of user u towards tgt (since 2.1.0; by default clear).
before_link_failure(u, t)
¶
User u has a radio-link failure while no attempt is in flight (called first).
recovers_into(u, cell) -> bool
¶
True if a failed UE that selects cell recovers into a prepared cell.
ConditionalProcedure
¶
DAPSProcedure
¶
Bases: BaselineProcedure
DAPS handover: a baseline handover whose source link stays up during the execution.
LTMProcedure
¶
Bases: ConditionalProcedure
L1/L2-triggered mobility: candidates configured on L3 reports, cell switch on L1 reports.
after_execution(u, tgt, t)
¶
TS 38.331 5.3.5.18.6 keeps the ltm-Config across an LTM cell switch: the other candidates stay configured; only the new serving cell leaves the candidate set (since 2.1.0; 2.0.0: all released).
CLTMProcedure
¶
Bases: LTMProcedure
Conditional LTM: the UE executes a configured candidate when the L1 switch condition holds.
receive_during_t310(eng, u, wait, t) -> bool
¶
A command that reaches user u while T310 runs, with protocol.cmd_during_t310 = "receive".
TS 38.331 5.3.5.5.2: the UE executes a received reconfiguration with sync and stops T310. The command is received at the first step with link-monitoring SINR >= Qout (the 10 % BLER reference of TS 38.133 8.1; HARQ and RLC retransmissions go on while T310 runs). Returns True while the command waits, and the caller then does nothing; after reception (T310 stopped) or with the default "lost" it returns False and the caller acts as before.
handoverlab.engine.batch
¶
Several engines on one world, advanced by one step loop.
All rules of a job run on the same radio samples. EngineBatch
stacks the per-user state of the radio-link monitoring of K engines into
arrays of shape (K, U); each engine keeps views of its own row, so all of
its event handling (executions, failures, procedures, measurement instants,
outcome labels) runs unchanged. Per step, one set of array operations then
serves all K engines:
- serving SINR of every user of every engine;
- the Qout / Qin filters, the N310 / T310 logic and the expiry of T310;
- the check for ends of executions and re-establishments.
Only the interference sum of each engine (ln @ rc, a matrix-vector
product per engine, as in Engine) and the
work at events stay per engine. The interference weight of the serving
cells changes only at measurement instants and when a user changes cell, so
it is gathered only then.
Every operation is element by element on the same values and in the same order as in a single engine, so results are bit-identical; the golden event-log fingerprints and the regression tests check this. Engines in debug mode are not batched.
handoverlab.engine.metrics
¶
Counters of a run and the metric catalogue.
Rates are given per UE-minute of the measured part of the run (after the
warm-up), with the suffix _pm. Counts start with n_.
Counters
¶
Bases: dict
Integer counters of a run; every name starts at zero.
summarize(C: Counters, acc: dict, ue_min: float, ue_s: float, n_users: int, step_s: float, open_preparations: int, n_sites: int = 1, l1_per_meas: int = 1, power=(0.0, 0.0)) -> Dict[str, float]
¶
Metric dictionary of a finished run (see METRICS).