Skip to content

Configuration

One dataclass, NRConfig, configures every module: numerology, carrier and TDD pattern, MAC timing, HARQ and RLC, the PHY tables, the radio and cell layout, and the application fields. Do not add a second config class; new options become NRConfig fields.

from isaac_net import NRConfig, make_engine
from isaac_net.core import multicell, netslot_compat

cfg = NRConfig(mu=1, bandwidth_mhz=20, tdd_pattern="DDDSU", n_harq=16, mcs_table=2, dl=True)
print(cfg.summary())                     # 51 PRB in 13 RBGs, 200 slots per 100 ms step, ...
cfg2 = cfg.with_(bler_target=0.01)       # modified copy
net = make_engine("L2", E, R, "cuda", cfg)

Presets

Each preset returns an NRConfig and takes keyword overrides, for example netslot_compat(n_harq=4).

Preset What it is
netslot_compat() the geometry and timing of the legacy slot-level model with the 3GPP PHY: one HARQ process, 50 PRBs in 5 subbands of 10, DDDSU
lena_like() (alias lena_match) the ns-3 5G-LENA reference scenario as far as the model allows; needs the locally generated 5G-LENA BLER tables
lena_validation() lena_like() in the geometry of the 5G-LENA validation sweep (fading off, whole-band UE power, per-UE link budgets)
lena_match_v2(), lena_validation_v2() the same two with 5G-LENA's MAC behavior under load (per-RBG PF, TDMA UL retransmissions, the SR / BSR grant pipeline and the RLC tail stall; load gap), which matches 5G-LENA in loaded cells too
srsran_like(), oai_like() uplink latency fitted to public srsRAN and OAI measurements (see Public-data calibration)
multicell(n) a hexagonal cluster of n cells at 100 m spacing, thermal noise, same-slot interference, uplink power control and A3 handover; runs on L2 (up to 7 cells) and on L2-legacy

Strict mode

Every level reads only some fields, listed by fields_read_by(level). The prototype levels read the application fields and their own parameters, because their radio and MAC are fixed, while L2 reads the frame, NR, link and radio fields. A field that a level ignores is ignored silently by default. NRConfig.unused_fields(level) returns the fields that are set away from their defaults and that the level ignores, and make_engine(..., strict=True) raises a ValueError naming them:

cfg = NRConfig(mcs_table=2, l0_loss=0.1)
cfg.unused_fields("L0")                  # ['mcs_table']
make_engine("L0", E, R, "cpu", cfg, strict=True)   # ValueError: level L0 ignores these config fields: mcs_table

Independently of strict mode, the prototype levels, the surrogates and the bounds refuse a config whose frame_buffer, timeout_steps or control_step_ms differ from their compiled constants (16, 20 and 100 ms), and single-cell levels refuse n_cells > 1.

All fields

The tables below are generated from isaac_net/core/config.py when the docs are built. "Read by" names the engines that read the field: the application fields are read by every level, and "L2-legacy multi-cell" is L2-legacy with a non-legacy cell setting such as multicell(n). Configurability discusses the modelling choices behind many of them.

Numerology and carrier

Field Default Read by Meaning
mu 1 L2, L2-legacy multi-cell SCS = 15 * 2^mu kHz, mu in
bandwidth_mhz 20 L2
n_prb None L2 override of the 38.101 N_RB (e.g. 50 to match LENA RbOverhead=0.1)
rbg_size None L2 override of the 38.214 RBG size (subband = RBG)
rbg_config 1 L2 38.214 RBG configuration 1 or 2

Duplexing (TDD pattern, or FDD with a paired DL carrier)

Field Default Read by Meaning
duplex "tdd" L2 "tdd": tdd_pattern below; "fdd": the UL carrier is all-U and a paired DL carrier is all-D, so every slot carries both (the TDD fields are unused)
dl_n_prb None L2 fdd: PRBs of the DL carrier (override); None = from dl_bandwidth_mhz
dl_bandwidth_mhz None L2 fdd: DL carrier bandwidth (38.101 N_RB at scs_khz); None = the UL carrier
tdd_pattern "DDDSU" L2, L2-legacy multi-cell one letter per slot, D / S / U, repeated
special_split (10, 2, 2) L2, L2-legacy multi-cell S slot symbols (DL, guard, UL)
special_dl_data True L2 S slot DL symbols carry PDSCH
special_ul_data False L2, L2-legacy multi-cell S slot UL symbols carry PUSCH (LENA: no)
dl_ctrl_symbols 1 L2 PDCCH symbols at the start of D / S slots
ul_data_symbols 12 L2, L2-legacy multi-cell PUSCH symbols in a U slot (14 - PUCCH - SRS)
ul_mini_slot_symbols None L2 mini-slot (type B) grants, NR engine: None = one scheduling occasion per slot; 2, 4 or 7 = every UL data slot is split into occasions of that many symbols (occasion_symbols), each its own grant, TB and decode (docs/configurability.md "Mini-slot grants")
mini_slot_dl False L2 split the DL data slots into occasions of ul_mini_slot_symbols too
dmrs_re_per_prb 12 L2 N_DMRS^PRB in the 38.214 TBS formula (1 DMRS symbol, type 1)
overhead_re_per_prb 0 L2 N_oh^PRB (xOverhead)

MAC timing (slots)

Field Default Read by Meaning
k1 2 L2 PDSCH -> HARQ-ACK (minimum; ACK goes to next UL-capable slot)
k2 2 L2 UL DCI -> PUSCH
gnb_proc_slots 1 L2 gNB decode / scheduling latency
sr_period_slots 5 L2 SR opportunity: first UL-capable slot of every period window
sr_grant_delay_slots None L2 SR -> first PUSCH; default gnb_proc + k2
ul_harq_rtt_slots None L2 PUSCH -> earliest retx; default gnb_proc + k2
cqi_period_slots 10 L2 DL CQI report period (reports ride the next UL-capable slot)
proactive_grant "off" L2 "off" | "every_ul_slot" | "per_period": UL grant without SR at the first UL data slot of each TDD period (OAI-like); unused grants are not modelled
proc_offset_ms 0.0 L2 fixed stack processing delay added to every frame completion

HARQ / RLC

Field Default Read by Meaning
n_harq 16 L2 HARQ processes per UE per direction (1 = head-of-line mode)
max_harq_tx 4 L2 transmissions per TB including the first (LENA maxHarqReTx=3)
harq_combining "cc" L2 "cc" (chase: add linear effective SINR), "ir_lena" (EESM over all tx + equivalent MCS at R/ntx, 5G-LENA NrEesmIr), "none"
harq_fail "rlc_am" L2 "rlc_am": resend after rlc_retx_slots, "drop": RLC UM loss
rlc_retx_slots 50 L2
discard "purge" L2 "purge": drop queued frames older than timeout (legacy NetSlot); "none": never purge; "pdcp_arrival": drop ARRIVING frames while the head-of-line frame is older than timeout (5G-LENA UM PDCP discard)
Field Default Read by Meaning
mcs_table 1 L2 38.214 Table 5.1.3.1-1 (64QAM) or -2 (256QAM)
eff_sinr "eesm" L2 "eesm" or "mean_db" (legacy NetSlot)
bler_source "pdsch" L2 "pdsch": Sionna PDSCH curves both directions; "lena": 5G-LENA EESM tables (local extraction); "sionna_label": audit only
tbs_mode "38214" L2 "38214" exact TBS, or "lena": floor((12-ref_sc)RBsymQmR/8) - CRC
lena_ref_sc_per_rb 1 L2 NrAmc NumRefScPerRb (only for tbs_mode="lena")
bler_target 0.1 L2
ul_mcs_max None L2 MCS index cap (public-data calibration: bench open-source UL SE ~2.4-3)
dl_mcs_max None L2
cqi_table "mcs" L2 DL CQI: "mcs" = the UE reports the highest MCS meeting the BLER target per RBG; "38214" = 4-bit CQI of TS 38.214 Table 5.2.2.1-2 (-3 with mcs_table=2), mapped to the highest MCS whose spectral efficiency is <= the CQI's
olla True L2
olla_up_db 0.05 L2 down step = up * (1 - target) / target
scheduler "pf" L2 "pf" proportional fair (metric from pf_metric), "pf_wideband" (= pf with pf_metric="wideband"), "maxci" (max rate, no fairness), "rr" (round robin: the robot served longest ago first, channel-blind), "qos" (5G-LENA NrMacSchedulerOfdmaQos: PF weighted by the 5QI priority and the delay budget of the queued message classes, docs/configurability.md)
pf_metric "subband" L2 "subband" (frequency-selective) or "wideband" (5G-LENA OFDMA PF)
pf_window 100.0 L2 EWMA window in scheduled slots of that direction
retx_priority True L2

SU-MIMO rank model (NR engine; docs/configurability.md "MIMO rank"); n_layers_max = 1 = one layer, bitwise

Field Default Read by Meaning
n_layers_max 1 L2 1 or 2: largest rank of a TB; the rank is chosen per new TB at link adaptation
rank_rule "sinr_los" L2 rank 2 when "sinr": wideband SINR >= rank_sinr_min_db; "los": Rician K of the link < rank_k_max_db (NLOS = K 0, rich scattering); "sinr_los": both
rank_sinr_min_db 10.0 L2 wideband link-adaptation SINR (per PRB, linear mean over the RBGs) for rank 2
rank_k_max_db 3.0 L2 K (dB) below which a link counts as rich scattering (no Rician K: K = 0)
rank_layer_penalty_db 3.0 L2 per-layer SINR = SINR - 10 log10(rank) - this (inter-layer interference)
ul_mimo False L2 rank 2 in the UL (needs two UE antennas)
dl_mimo True L2 rank 2 in the DL (needs dl=True)

Scheduler="qos" (read only then): message class c = clamp(priority, 0, qos_classes - 1) per message

Field Default Read by Meaning
qos_classes 2 L2 Q message classes (class 0 = highest priority)
qos_priority (10, 70) L2 3GPP priority level P per class (1..99, lower = more important); the default is 5QI 5 (IMS signalling) vs 5QI 7 (voice / video / gaming)
qos_pdb_ms (math.inf, math.inf) L2 packet delay budget per class (ms); finite = delay-critical class (5G-LENA DC-GBR) with the delay-budget factor, inf = factor 1
qos_gamma 1.0 L2 rate exponent of the QoS metric (5G-LENA m_alpha)

5G-LENA MAC behavior under load (docs/fidelity-load-gap.md); defaults = the engine before these switches

Field Default Read by Meaning
pf_update "slot" L2 "slot": PF metric fixed within a slot, average updated with the served bytes; "rbg": 5G-LENA OFDMA PF, the winner's average is updated with its granted bytes after every RBG (RBGs spread over backlogged UEs)
pf_avg_idle "decay" L2 "decay": every UE's PF average moves every data slot; "freeze": only UEs with new data to schedule update it (5G-LENA active list)
ul_retx_sched "ofdma" L2 "ofdma": UL retx share the slot with new data; "tdma": one UL retx per slot (oldest NACK first) on every data symbol, nothing else in its slot
ul_amc_alloc "current" L2 UL MCS for the PRBs of the current grant, or "previous": for the PRBs of the UE's previous PUSCH (5G-LENA NrAmc), TB sized for the current one
ul_grant_model "lumped" L2 "lumped": SR -> grant after sr_grant_delay_slots, BSR = exact buffer; "bsr": 5G-LENA pipeline (SR bootstrap grant, 38.321 short-BSR levels rounded up with a report delay, padded over-grants, RLC tail stall); makes sr_grant_delay_slots and proactive_grant unused
sr_boot_slots 6 L2 bsr: SR -> first PUSCH slot the bootstrap grant may use
sr_boot_bytes 17 L2 bsr: bootstrap grant after an SR (5G-LENA srGrantSize 12 + 2 RLC + 3 MAC)
bsr_delay_slots 10 L2 bsr: PUSCH carrying a BSR -> first PUSCH scheduled with it (2 UL slots)
bsr_hdr_bytes 8 L2 bsr: UE adds short BSR (5) + MAC subheader (3) to the reported buffer
bsr_est_hdr_bytes 5 L2 bsr: scheduler adds RLC (2) + MAC subheader (3) to the reported buffer
rlc_tail_bytes 16 L2 bsr: RLC header residue the draining TB leaves behind (0 = no tail stall)
rlc_tail_timer_ms 10.0 L2 bsr: RLC buffer-status timer that reports the residue

Radio model inside the engine

Field Default Read by Meaning
snr_ref_prbs 10 L2, L2-legacy multi-cell input UL SNR = full UE power over this many PRBs
ul_power "allocated" L2 "allocated": UE power split over its grant; "whole_band": fixed PSD
phr_cap True L2 UL power-headroom cap on RBGs per UE (NetSlot; 5G-LENA has none)
phr_min_db 3.0 L2 cap rule: per-PRB SNR (at the snr_ref_prbs split) >= this
fading True L2
fading_rho_per_ms 0.93 ** (1 / 2.5) L2 AR(1) per-subband Rayleigh; 0.93 per 2.5 ms (35 Hz)
ue_speed_mps None L2 set: fading_rho_per_ms = J0(2 pi f_D 2.5 ms)^(1/2.5), f_D = v f_c / c
carrier_ghz 3.5 L2 carrier for the Doppler of ue_speed_mps (3 m/s at 3.5 GHz = 35 Hz) and of the tr38901 path loss
fading_doppler "global" L2 "global": one rho for all robots; "per_robot": rho from each robot's own speed (velocity input or consecutive poses), NR engine with pose input
doppler_min_speed_mps 0.0 L2 per_robot: speed floor (0 = a still robot's fading is frozen)
fading_rician False L2 Rician fast fading (NR engine, docs/channels.md): per subband |sqrt(K/(K+1)) e^{j phi} + sqrt(1/(K+1)) h|^2, h the AR(1) Rayleigh state
rician_k_db None L2 fading_rician: fixed K (dB) for every link; None = K from the LOS state
rician_k_from_los True L2 K log-normal per link (TR 38.901 Table 7.5-6, tr38901_scenario) where the radio reports LOS and no blockage, else K = 0; False (or no LOS state): K = 0
rician_k_ramp_slots 4 L2 K moves linearly to a new target over this many slots (0 = at once)
fading_freq_corr False L2 frequency-correlated fading across the subbands (NR engine, docs/channels.md "Frequency-selective fading"): the AR(1) innovation becomes L z with L the Cholesky factor of the subband correlation of a delay spread
fading_delay_spread_ns None L2 fixed rms delay spread (ns) of every link; None = per-link draw
fading_ds_from_los True L2 per-link log-normal DS (TR 38.901 Table 7.5-6 lgDS of tr38901_scenario), the LOS or NLOS value by the radio's LOS state (NLOS while there is none)
fading_pdp "exponential" L2 power-delay profile behind the correlation (exponential only)
fading_ds_grid (3.0, 3000.0, 16) L2 per-link DS: (min ns, max ns, points) log grid of precomputed L
inf_hall_volume_m3 None L2 InF lgDS hall volume V and total surface S (walls + floor + ceiling);
inf_hall_surface_m2 None L2 None = the Table 7.8-7 hall of the scenario (V/S = 4.0 or 4.55)
inf_lg_ds None L2 InF: direct mean lgDS = log10(DS / 1 s) for LOS and NLOS (overrides V/S)
dl_snr_offset_db 10.0 L2 step(): default DL per-PRB SNR = UL input SNR + offset
Field Default Read by Meaning
noise_model "fixed" L2, L2-legacy multi-cell "fixed": ni_fixed_dbm over snr_ref_prbs PRBs; "thermal": -174 dBm/Hz + NF
ni_fixed_dbm -90.0 L2 noise + interference floor over snr_ref_prbs PRBs (legacy NetSlot)
gnb_nf_db 5.0 L2, L2-legacy multi-cell
ue_nf_db 9.0 L2
ue_tx_dbm 23.0 L2, L2-legacy multi-cell
gnb_tx_dbm 43.0 L2 DL total power, spread evenly over all PRBs

Overheads

Field Default Read by Meaning
tb_overhead_bytes 0 L2 MAC subheaders / MAC CEs / RLC header per TB
pkt_payload_bytes 1400 L2 application frame split into packets of this payload
pkt_overhead_bytes 0 L2 per-packet bytes on the air (frame header, UDP/IP, PDCP, RLC SDU hdr)
Field Default Read by Meaning
pl_const_db 40.0 L2, L2-legacy multi-cell PL = pl_const_db + 10 pathloss_exp log10(d), d >= 1 m
pathloss_exp 3.5 L2, L2-legacy multi-cell
shadow_sigma_db 6.0 L2, L2-legacy multi-cell one spatially correlated field per (env, cell), sum of plane waves
shadow_modes 8 L2, L2-legacy multi-cell plane waves per field (every channel model's fields)
shadow_dcorr_m 10.3 L2, L2-legacy multi-cell 1/e decorrelation distance of the log_distance shadowing (10.3 = the legacy 20-60 m wavelength band, bitwise; POWDER fit: 20-40 m)
shadow_acf "sos" L2, L2-legacy multi-cell "sos": legacy band scaled to shadow_dcorr_m; "exp": exp(-r / shadow_dcorr_m)
shadow_white_frac 0.0 L2, L2-legacy multi-cell share of the shadowing variance in a component decorrelated within
shadow_white_dcorr_m 1.0 L2, L2-legacy multi-cell this distance (exp ACF); POWDER: about 0.5 of the variance

Channel model (radio.py; see docs/channels.md)

Field Default Read by Meaning
channel "log_distance" L2, L2-legacy multi-cell "log_distance" (above) | "tr38901" | "radio_map"; "tr38901_inf_sh" etc. is shorthand for channel="tr38901", tr38901_scenario="InF-SH"
tr38901_scenario "InF-SH" L2 RMa, UMa, UMi, InH (mixed office), InF-SL, InF-DL, InF-SH, InF-DH
tr38901_los "stochastic" L2 "stochastic" (Table 7.4.2-1, spatially consistent) | "los" | "nlos"
gnb_height_m None L2, L2-legacy multi-cell None: the scenario default (tr38901), else 2-D geometry (other models)
ue_height_m 1.5 L2, L2-legacy multi-cell robot antenna height (tr38901 and blockage)
o2i_indoor_frac 0.0 L2 tr38901 UMa / UMi / RMa: share of robots indoors (O2I penetration)
o2i_model "low" L2 "low" | "high" (Table 7.4.3-2; RMa allows only "low")
inf_clutter_density None L2 InF r, d_clutter, h_c; None = Table 7.8-7 values of the scenario
inf_clutter_size_m None L2
inf_clutter_height_m None L2
radio_map_path None L2 channel="radio_map": .npz / .pt with gain_db [C,H,W] and bounds (x0,y0,x1,y1); "synthetic" = the shipped 2-cell test map
blockage False L2, L2-legacy multi-cell add-on to every channel: other robots are spheres that block the link
blockage_radius_m 0.3 L2
blockage_loss_db 20.0 L2 extra loss on a link blocked by at least one robot body

Obstacles and NLOS (channels/los.py, channels/blockage.py; see docs/obstacles.md)

Field Default Read by Meaning
los_source "stochastic" L2, L2-legacy multi-cell LOS state: "stochastic" (tr38901 Pr_LOS, today) | "map" (radio map los_prob) | "raycast" (radio map obstacle_z height map) | "callback" (a blocked_fn, RadioMC.set_los_callback); map / raycast read radio_map_path
los_raycast_samples 32 L2 raycast: samples along each robot-gNB segment
los_diffraction False L2 raycast: ITU-R P.526 knife-edge loss from the clearance (LOS -> NLOS ramp)
los_soft False L2 tr38901 with los_source="stochastic": Sec. 7.6.3.3 soft LOS blend
nlos_extra_loss_db 0.0 L2 log_distance with a geometric LOS state: extra loss on NLOS links
blockage_model "sphere" L2 blockage=True: "sphere" (above) | "screen" (38.901 model B, robots and step(blockers=) as screens) | "stochastic" (38.901 model A)
blocker_size_m ((0.6, 1.5), (0.3, 1.7), (4.8, 1.4)) L2 screen (w, h) m per class: robot, human, vehicle (Table 7.6.4.2-5 for human and vehicle)
blockage_max_db 40.0 L2 screen / stochastic: cap of the summed blockage loss per link

Default: one gNB at the origin with the fixed noise floor, which is the legacy NetSlot geometry. multicell() gives the multi-cell preset. n_cells > 1 runs on level "L2" (NR engine, UL and DL interference) and on "L2-legacy" (NetSlotMC, UL only). TTT and interruption are in ms: NetSlotMC counts them in UL slots (ttt_slots, ho_int_slots), the NR engine in slots of slot_ms.

Field Default Read by Meaning
n_cells 1 L2, L2-legacy multi-cell 1..7
cell_layout "custom" L2, L2-legacy multi-cell "hex" | "grid" | "custom"
cell_positions_m ((0.0, 0.0),) L2, L2-legacy multi-cell custom: one (x, y) per cell, env-local metres
cell_isd_m 100.0 L2, L2-legacy multi-cell hex inter-site distance (>= 100 m: see multicell())
cell_center_m (75.0, 75.0) L2, L2-legacy multi-cell hex: cluster centroid
cell_arena_m 150.0 L2, L2-legacy multi-cell grid: square arena tiled by ceil(sqrt(C)) columns
gnb_antenna "isotropic" L2, L2-legacy multi-cell gNB antenna: "isotropic" (0 dBi) | "sector": TR 38.901 Table 7.3-1 element (65 deg HPBW, 30 dB front-back), added to every link (channels/antenna.py)
cell_azimuth_deg None L2 sector: boresight azimuth per cell (deg, from +x toward +y); None = 30, 150, 270 cycled over the cells (the 3-sector site convention)
cell_tilt_deg 0.0 L2 sector: downtilt (deg below the horizon), one value or one per cell
gnb_antenna_gain_dbi 8.0 L2 sector: maximum element gain G_E,max (Table 7.3-1)
ul_interference True L2, L2-legacy multi-cell thermal noise only: add same-slot other-cell UL interference
dl_interference True L2 thermal noise only: same for the DL (NR engine)
li_alpha 1.0 L2, L2-legacy multi-cell link adaptation uses an EWMA of the measured N+I; 1 = previous slot
ul_pc None L2, L2-legacy multi-cell UL open-loop fractional power control P = min(Pmax - split, P0 + alpha PL) per subband; None = ON when n_cells > 1, OFF for one cell (legacy)
ul_pc_p0_dbm -88.0 L2, L2-legacy multi-cell per subband (10 PRB); about 15 dB SNR per subband at alpha = 1
ul_pc_alpha 1.0 L2, L2-legacy multi-cell
ul_tpc False L2 UL closed-loop power control (TS 38.213 Sec. 7.1.1) on top of ul_pc: per-robot TPC offset from the gNB's PUSCH SINR measurement (NR engine; needs ul_pc on)
ul_tpc_mode "accumulate" L2 "accumulate": f += step, clamped; "absolute": f = step
ul_tpc_target_db None L2 target PUSCH SINR per PRB at the gNB; None = the 10 % BLER SINR of the middle MCS of mcs_table (MCS 14: 6.3 dB table 1, 12.8 dB table 2, pdsch)
ul_tpc_steps_db None L2 command set (dB); None = (-1, 0, 1, 3) accumulate, (-4, -1, 1, 4) absolute (38.213 Table 7.1.1-1)
ul_tpc_delay_slots None L2 PUSCH -> its command takes effect; None = k2
ul_tpc_range_db 20.0 L2 |f| clamp (accumulated offset within the UE power range)
a3_offset_db 0.0 L2, L2-legacy multi-cell A3: neighbour > serving + offset + hysteresis ...
a3_hyst_db 3.0 L2, L2-legacy multi-cell
a3_ttt_ms 300.0 L2, L2-legacy multi-cell ... held for the time-to-trigger
ho_interruption_ms 40.0 L2, L2-legacy multi-cell robot cannot be scheduled after a handover
ho_rlc "carry" L2, L2-legacy multi-cell "carry": lossless, queued frames continue at the target; "flush": dropped
a3_min_target_rsrp_dbm None L2, L2-legacy multi-cell A3 target admission (5G-LENA MinTargetRsrpDbm): a neighbour whose RSRP (gNB EPRE + path gain, dBm per subcarrier) is below it is not a target; also the floor of the RLF cell search. None = no floor
rlf False L2
rlf_qout_db -8.0 L2 out-of-sync SINR (TS 38.133 Qout: 10% hypothetical PDCCH BLER, about -8 dB)
rlf_qin_db -6.0 L2 in-sync SINR (TS 38.133 Qin: 2% hypothetical PDCCH BLER, about -6 dB)
n310 1 L2 TS 38.331 N310 / N311 (network-configured; n1 is the smallest value)
n311 1 L2
t310_ms 1000.0 L2 TS 38.331 T310 (network-configured, ms0 to ms6000)
t311_ms 3000.0 L2 TS 38.331 T311 (network-configured, ms1000 to ms30000)
reest_delay_ms 40.0 L2 cell found -> service at that cell (random access and RRC re-establishment)
rlf_rlc None L2 on RLF: "carry" (PDCP AM recovery) or "flush"; None = follow ho_rlc

Access state machine (core/access.py, level "L2"): RACH / connection setup and connected-mode DRX

Off by default (every robot always connected and awake, the engine before these fields, bitwise).

Field Default Read by Meaning
rach False L2 contention-based random access (TS 38.321 Sec. 5.1) before a robot is served
rach_occasion_slots 20 L2 RACH occasion (RO) period; ROs at the first UL-capable slot of every period window (a multiple of the TDD period; 20 slots = 10 ms at mu = 1)
rach_preambles 64 L2 contention-based preambles per cell and RO (TS 38.211: 64 per cell)
rach_rar_window_slots 10 L2 preamble -> RAR (the RAR is taken at the end of ra-ResponseWindow)
rach_msg3_slots 10 L2 RAR -> contention resolution (Msg3 on PUSCH, Msg4); then the robot is served
rach_backoff_ms 20.0 L2 backoff after a collision: uniform in [0, rach_backoff_ms] (38.321 Sec. 5.1.4, backoff indicator; 20 ms = BI index 2 of Table 7.2-1)
rach_max_attempts 10 L2 preambleTransMax; a procedure that reaches it fails and restarts
rach_initial "connected" L2 state after a reset: "connected" (as without RACH) or "idle" (power-on)
rach_release_after_ms None L2 RRC release to idle after this inactivity; None = never
drx False L2 connected-mode DRX (TS 38.321 Sec. 5.7)
drx_inactivity_ms 100.0 L2 drx-InactivityTimer: awake this long after the last scheduling activity
drx_cycle_ms 160.0 L2 drx-LongCycle
drx_on_ms 10.0 L2 drx-onDurationTimer
drx_short_cycle_ms None L2 drx-ShortCycle; None = no short cycle
drx_short_cycles 2 L2 drx-ShortCycleTimer in short cycles
drx_start_offset_ms 0.0 L2 drx-StartOffset (on-durations start at slot offset mod cycle)
drx_ul_wake "sr" L2 UL data while dormant: "sr" wakes the robot at once (a pending SR is Active Time, 38.321 Sec. 5.7), "on_duration" waits for the next on-duration

Directions and application

Field Default Read by Meaning
ul True L2
dl False L2
control_step_ms 100.0 every level
frame_buffer 16 every level frames per robot per direction
timeout_steps 20 every level application deadline in control steps
msg_sizes (4000.0, 30000.0) # bytes of traffic classes 1, 2, ... (Requests.send = class index) every level bytes of traffic classes 1, 2, ... (Requests.send = class index)
traffic None L2 traffic models run inside the step (traffic.TrafficModel), level L2 only; None = policy messages only. A model or a list is turned into a tuple

Delay levels (make_engine fills the level params from these when params is None)

Field Default Read by Meaning
l0_delay_median_steps 0.05 L0 L0: i.i.d. lognormal delay, median in control steps
l0_delay_log_sigma 0.5 L0 L0: sigma of the log delay
l0_loss 0.0 L0 L0: i.i.d. loss probability
dr_delay_median_steps (0.05, 10.0) L0DR L0DR: per-env median delay range, log-uniform at every reset
dr_delay_log_sigma (0.2, 1.2) L0DR L0DR: per-env sigma of the log delay, uniform
dr_loss (0.0, 0.2) L0DR L0DR: per-env loss probability, uniform
l1_eta 0.9 L1 L1: goodput factor on 0.75 log2(1 + SNR) (all backends, triton included)

Randomness and prototype timing

Field Default Read by Meaning
seed None every level engine seed (make_engine(seed=...) overrides it); None = drawn from the global torch RNG at construction
rng "engine" every level every level (the NR engine too): "engine" = every draw from the engine's counter-based streams keyed by (seed, env, episode, call), so a policy's use of the global RNG never changes the network; "global" = the earlier behavior (stepping draws from the global torch RNG)
proto_ul_slots_per_step None every level prototype levels (L1, L2-legacy, QA): UL slots per control step; None = control_step_ms / 2.5 ms (the legacy DDDSU UL spacing)

Edge-computing loop (core/edge.py): make_engine wraps any level in EdgeLoop when set

Field Default Read by Meaning
edge None every level
background None every level core.background.BackgroundConfig: non-robot UEs sharing each cell
energy None every level core.energy.EnergyConfig: per-robot radio energy and battery
wifi None no engine yet WifiConfig (core/wifi/config.py) for level "WIFI"; None = WifiConfig()
fidelity None no engine yet adaptive / mixed fidelity: a core.adaptive.FidelityConfig for make_adaptive

Class and functions

NRConfig dataclass

NRConfig(mu: int = 1, bandwidth_mhz: int = 20, n_prb: int | None = None, rbg_size: int | None = None, rbg_config: int = 1, duplex: str = 'tdd', dl_n_prb: int | None = None, dl_bandwidth_mhz: int | None = None, tdd_pattern: str = 'DDDSU', special_split: tuple = (10, 2, 2), special_dl_data: bool = True, special_ul_data: bool = False, dl_ctrl_symbols: int = 1, ul_data_symbols: int = 12, ul_mini_slot_symbols: int | None = None, mini_slot_dl: bool = False, dmrs_re_per_prb: int = 12, overhead_re_per_prb: int = 0, k1: int = 2, k2: int = 2, gnb_proc_slots: int = 1, sr_period_slots: int = 5, sr_grant_delay_slots: int | None = None, ul_harq_rtt_slots: int | None = None, cqi_period_slots: int = 10, proactive_grant: str = 'off', proc_offset_ms: float = 0.0, n_harq: int = 16, max_harq_tx: int = 4, harq_combining: str = 'cc', harq_fail: str = 'rlc_am', rlc_retx_slots: int = 50, discard: str = 'purge', mcs_table: int = 1, eff_sinr: str = 'eesm', bler_source: str = 'pdsch', tbs_mode: str = '38214', lena_ref_sc_per_rb: int = 1, bler_target: float = 0.1, ul_mcs_max: int | None = None, dl_mcs_max: int | None = None, cqi_table: str = 'mcs', olla: bool = True, olla_up_db: float = 0.05, scheduler: str = 'pf', pf_metric: str = 'subband', pf_window: float = 100.0, retx_priority: bool = True, n_layers_max: int = 1, rank_rule: str = 'sinr_los', rank_sinr_min_db: float = 10.0, rank_k_max_db: float = 3.0, rank_layer_penalty_db: float = 3.0, ul_mimo: bool = False, dl_mimo: bool = True, qos_classes: int = 2, qos_priority: tuple = (10, 70), qos_pdb_ms: tuple = (math.inf, math.inf), qos_gamma: float = 1.0, pf_update: str = 'slot', pf_avg_idle: str = 'decay', ul_retx_sched: str = 'ofdma', ul_amc_alloc: str = 'current', ul_grant_model: str = 'lumped', sr_boot_slots: int = 6, sr_boot_bytes: int = 17, bsr_delay_slots: int = 10, bsr_hdr_bytes: int = 8, bsr_est_hdr_bytes: int = 5, rlc_tail_bytes: int = 16, rlc_tail_timer_ms: float = 10.0, snr_ref_prbs: int = 10, ul_power: str = 'allocated', phr_cap: bool = True, phr_min_db: float = 3.0, fading: bool = True, fading_rho_per_ms: float = 0.93 ** (1 / 2.5), ue_speed_mps: float | None = None, carrier_ghz: float = 3.5, fading_doppler: str = 'global', doppler_min_speed_mps: float = 0.0, fading_rician: bool = False, rician_k_db: float | None = None, rician_k_from_los: bool = True, rician_k_ramp_slots: int = 4, fading_freq_corr: bool = False, fading_delay_spread_ns: float | None = None, fading_ds_from_los: bool = True, fading_pdp: str = 'exponential', fading_ds_grid: tuple = (3.0, 3000.0, 16), inf_hall_volume_m3: float | None = None, inf_hall_surface_m2: float | None = None, inf_lg_ds: float | None = None, dl_snr_offset_db: float = 10.0, noise_model: str = 'fixed', ni_fixed_dbm: float = -90.0, gnb_nf_db: float = 5.0, ue_nf_db: float = 9.0, ue_tx_dbm: float = 23.0, gnb_tx_dbm: float = 43.0, tb_overhead_bytes: int = 0, pkt_payload_bytes: int = 1400, pkt_overhead_bytes: int = 0, pl_const_db: float = 40.0, pathloss_exp: float = 3.5, shadow_sigma_db: float = 6.0, shadow_modes: int = 8, shadow_dcorr_m: float = 10.3, shadow_acf: str = 'sos', shadow_white_frac: float = 0.0, shadow_white_dcorr_m: float = 1.0, channel: str = 'log_distance', tr38901_scenario: str = 'InF-SH', tr38901_los: str = 'stochastic', gnb_height_m: float | None = None, ue_height_m: float = 1.5, o2i_indoor_frac: float = 0.0, o2i_model: str = 'low', inf_clutter_density: float | None = None, inf_clutter_size_m: float | None = None, inf_clutter_height_m: float | None = None, radio_map_path: str | None = None, blockage: bool = False, blockage_radius_m: float = 0.3, blockage_loss_db: float = 20.0, los_source: str = 'stochastic', los_raycast_samples: int = 32, los_diffraction: bool = False, los_soft: bool = False, nlos_extra_loss_db: float = 0.0, blockage_model: str = 'sphere', blocker_size_m: tuple = ((0.6, 1.5), (0.3, 1.7), (4.8, 1.4)), blockage_max_db: float = 40.0, n_cells: int = 1, cell_layout: str = 'custom', cell_positions_m: tuple = ((0.0, 0.0),), cell_isd_m: float = 100.0, cell_center_m: tuple = (75.0, 75.0), cell_arena_m: float = 150.0, gnb_antenna: str = 'isotropic', cell_azimuth_deg: tuple | None = None, cell_tilt_deg: float | tuple = 0.0, gnb_antenna_gain_dbi: float = 8.0, ul_interference: bool = True, dl_interference: bool = True, li_alpha: float = 1.0, ul_pc: bool | None = None, ul_pc_p0_dbm: float = -88.0, ul_pc_alpha: float = 1.0, ul_tpc: bool = False, ul_tpc_mode: str = 'accumulate', ul_tpc_target_db: float | None = None, ul_tpc_steps_db: tuple | None = None, ul_tpc_delay_slots: int | None = None, ul_tpc_range_db: float = 20.0, a3_offset_db: float = 0.0, a3_hyst_db: float = 3.0, a3_ttt_ms: float = 300.0, ho_interruption_ms: float = 40.0, ho_rlc: str = 'carry', a3_min_target_rsrp_dbm: float | None = None, rlf: bool = False, rlf_qout_db: float = -8.0, rlf_qin_db: float = -6.0, n310: int = 1, n311: int = 1, t310_ms: float = 1000.0, t311_ms: float = 3000.0, reest_delay_ms: float = 40.0, rlf_rlc: str | None = None, rach: bool = False, rach_occasion_slots: int = 20, rach_preambles: int = 64, rach_rar_window_slots: int = 10, rach_msg3_slots: int = 10, rach_backoff_ms: float = 20.0, rach_max_attempts: int = 10, rach_initial: str = 'connected', rach_release_after_ms: float | None = None, drx: bool = False, drx_inactivity_ms: float = 100.0, drx_cycle_ms: float = 160.0, drx_on_ms: float = 10.0, drx_short_cycle_ms: float | None = None, drx_short_cycles: int = 2, drx_start_offset_ms: float = 0.0, drx_ul_wake: str = 'sr', ul: bool = True, dl: bool = False, control_step_ms: float = 100.0, frame_buffer: int = 16, timeout_steps: int = 20, msg_sizes: tuple = (4000.0, 30000.0), traffic: tuple | None = None, l0_delay_median_steps: float = 0.05, l0_delay_log_sigma: float = 0.5, l0_loss: float = 0.0, dr_delay_median_steps: tuple = (0.05, 10.0), dr_delay_log_sigma: tuple = (0.2, 1.2), dr_loss: tuple = (0.0, 0.2), l1_eta: float = 0.9, seed: int | None = None, rng: str = 'engine', proto_ul_slots_per_step: int | None = None, edge: EdgeConfig | None = None, background: object = None, energy: object = None, wifi: object | None = None, fidelity: object = None)

The one configuration of every isaac_net module: numerology, carrier and TDD pattern, MAC timing, HARQ
and RLC, PHY tables and link adaptation, radio and cell layout, and the application fields.

Every field has a default, so NRConfig() is a complete configuration (one cell, DDDSU at 30 kHz, 20 MHz).
The presets (netslot_compat, lena_like, lena_validation, srsran_like, oai_like, multicell) return an
NRConfig and take keyword overrides; cfg.with_(**changes) returns a modified copy. Derived quantities
(nprb, rbg, slots_per_step, ...) are properties computed from the fields. Each level reads only some fields
(fields_read_by); unused_fields(level) lists the non-default fields a level ignores, and
make_engine(..., strict=True) raises on them (strict=False, the default, warns once per config).
The scenario presets of core/scenarios.py (warehouse_private_5g, factory_inf, outdoor_campus, urllc_control) also
return an NRConfig. cfg.describe(level) prints a one-page summary, cfg.diff(other) lists the differing fields.

scs_khz property

scs_khz

Subcarrier spacing in kHz, 15 * 2^mu.

slot_ms property

slot_ms

Slot duration in ms, 1 / 2^mu.

nprb property

nprb

PRBs of the carrier: n_prb if set, else TS 38.101-1 N_RB for bandwidth_mhz at scs_khz.

rbg property

rbg

RBG size in PRBs (one subband): rbg_size if set, else the TS 38.214 value for nprb.

n_subbands property

n_subbands

Number of RBGs (subbands) of the carrier, ceil(nprb / rbg).

subband_prbs property

subband_prbs

PRBs per RBG (the last RBG may be smaller, 38.214 Sec. 5.1.2.2.1).

slots_per_step property

slots_per_step

ul_slots_per_step property

ul_slots_per_step

Slots per control step that carry uplink data symbols.

dl_slots_per_step property

dl_slots_per_step

Slots per control step that carry downlink data symbols.

sr_delay property

sr_delay

Slots from a scheduling request to the first PUSCH: sr_grant_delay_slots, default gnb_proc_slots + k2.

ul_rtt property

ul_rtt

Slots from a PUSCH to its earliest retransmission: ul_harq_rtt_slots, default gnb_proc_slots + k2.

subband_noise_dbm property

subband_noise_dbm

Noise per snr_ref_prbs-PRB subband at the gNB (fixed: ni_fixed_dbm; thermal: -174 dBm/Hz + NF).

ul_pc_on property

ul_pc_on

Whether uplink fractional power control is on: ul_pc, or by default on exactly when n_cells > 1.

ul_slot_ms property

ul_slot_ms

UL slot spacing of the multi-cell legacy engine NetSlotMC: control_step_ms / proto_slots_per_step (2.5 ms
by default). Converts the handover times to NetSlotMC's slots. The NR engine counts them in slot_ms
instead (CellAssociation(slot_ms=...)), so its values do not depend on this.

ttt_slots property

ttt_slots

A3 time-to-trigger a3_ttt_ms in NetSlotMC's UL slots (ul_slot_ms).

ho_int_slots property

ho_int_slots

Handover interruption ho_interruption_ms in NetSlotMC's UL slots (ul_slot_ms).

summary

summary()

One-line human-readable summary of the frame structure, HARQ, PHY and scheduler settings.

with_

with_(**kw)

A copy of this config with the given fields replaced (dataclasses.replace). An explicit
fading_rho_per_ms clears an inherited ue_speed_mps, which would otherwise recompute it.

unused_fields

unused_fields(level)

Fields set away from their defaults that make_engine(level, ..., self) ignores (make_engine warns once per
config with UnusedFieldsWarning, raises with strict=True, and stays silent with strict=None).

is_legacy_cell

is_legacy_cell()

One gNB at the origin with the fixed -90 dBm noise floor: the geometry of the prototype levels.

gnb_xy

gnb_xy()

gNB positions (env-local metres). hex: centre site then the first ring at 0, 60, ..., 300 deg,
first n_cells sites, centroid at cell_center_m (C = 3: equilateral triangle, C = 7: full cluster).
grid: centres of a ceil(sqrt(C))-column tiling of the arena. custom: cell_positions_m.

slot_symbols

slot_symbols(pos)

(dl_data_symbols, ul_data_symbols) of the slot at pattern position pos. FDD: every slot is a full D slot
on the DL carrier and a full U slot on the UL carrier.

ul_capable

ul_capable(pos)

Slot carries UL symbols (PUCCH for SR / HARQ-ACK / CQI) even if no PUSCH data (FDD: every slot).

noise_dbm_per_prb

noise_dbm_per_prb(rx='gnb')

Noise PSD per PRB (dBm). fixed: ni_fixed_dbm spread over snr_ref_prbs PRBs.

fields_read_by

fields_read_by(level, cfg=None)

NRConfig fields that the engine make_engine(level, ..., cfg) actually reads. Without cfg: the fields a level
may read under some configuration (whole groups); with cfg: only those its switches make it read (DL fields only
with dl=True, handover and interference fields only with several cells, the noise fields of the noise model, the
fields of the selected channel model, blockage and fading fields only when on, lena_ref_sc_per_rb only with
tbs_mode="lena", the closed-loop TPC fields only with ul_tpc, cqi_table only with dl, the antenna pattern fields only
with gnb_antenna="sector").

netslot_compat

netslot_compat(**kw)

Closest NRConfig to the legacy NetSlot (netsim.py L2): single HARQ process with head-of-line
blocking, DDDSU at mu=1, 50 PRB in 5 subbands of 10 PRB, 12 data symbols without DMRS
overhead, SR -> grant 2 UL slots, HARQ RTT 4 UL slots, 10 extra UL slots after exhaustion,
OLLA on, per-subband PF, legacy mean-dB effective SINR. PHY stays 3GPP MCS/TBS/BLER.

lena_like

lena_like(**kw)

Matches the ns3ref 5G-LENA v5.1 scenario (netslot-ref.cc) as far as the model allows:
50 PRB (RbOverhead 0.1) in 5 RBGs of 10 PRB, DDDSU, 13 UL data symbols (SRS off), 16 HARQ
processes, max 4 tx, retx at the next UL slot, RLC UM loss on HARQ exhaustion, no OLLA, no PHR
cap, wideband PF, 5G-LENA EESM tables + NrEesmIr combining + LENA TB size (bler_source="lena"
needs the local extraction; pass bler_source="pdsch", tbs_mode="38214", harq_combining="cc" to
run without it), PDCP discard of arriving SDUs, thermal noise with gNB NF 18.44 dB (= -90 dBm
per 10 PRB). Overheads are first estimates to calibrate against ns3ref (18 B frame header +
28 B UDP/IP + 2 B PDCP + 2 B RLC per 1400 B packet, 6 B MAC per TB).

lena_validation

lena_validation(**kw)

lena_like() in the ns3ref validation geometry (ns3ref_report 5.1 / 5.5): fading off, UE power
over the whole band, thermal noise NF 7 dB (-101.44 dBm per 10-PRB subband), per-UE link budgets
fed directly (tools/lena_replay.py), frames every 100 ms in phase.

srsran_like

srsran_like(**kw)

Fitted to srsRAN UL one-way delay (Zenodo 13754300, calib/params_latency.json 'srs'): SR period
20 ms, SR -> grant 1 UL slot, HARQ RTT 4 UL slots, BLER target 0.5%, processing offset 2.5 ms, no
proactive grants (W1 1.25 ms, KS 0.12 on the fit set). UL MCS capped at 15 (SE 2.41) for the
measured bench UL SE ceiling of about 2.4-3 (calib/params_link.json). The slot counts follow the mu and
tdd_pattern of the call (fit: mu=1, DDDSU).

oai_like

oai_like(**kw)

Fitted to OAI UL one-way delay, 5/10-slot TDD periods (params_latency.json 'oai'): proactive UL
grant once per TDD period, BLER target 0.5%, processing offset 2.25 ms (OAI 20-slot periods need
about 7.25 ms), SR period 20 ms / SR -> grant 10 UL slots (irrelevant with proactive grants),
HARQ RTT 4 UL slots; UL MCS capped at 15 (bench OAI UL SE ceiling median 2.36). The slot counts follow the
mu and tdd_pattern of the call (fit: mu=1, DDDSU).

multicell

multicell(n_cells=3, **kw)

Multi-cell preset (merged from multicell/): netslot_compat() MAC and PHY, a hex cluster of n_cells gNBs
at 100 m inter-site distance centred in the 150 m arena, thermal noise (NF 5 dB: -103.4 dBm per 10-PRB
subband) with same-slot UL interference, UL fractional power control (P0 -88 dBm per subband, alpha 1),
A3 handover (3 dB hysteresis, 300 ms TTT, 40 ms interruption) and lossless RLC carry-over.
Without power control, full-power robots near their own gNB dominate the interference and three cells
at 60 m ISD carry less than one (multicell_report); keep ul_pc on or the ISD at 100 m or more.
Runs on level "L2" (NR engine, this MAC with 3GPP MCS/TBS/BLER) and on "L2-legacy" (NetSlotMC).

fading_rho_from_speed

fading_rho_from_speed(speed_mps, carrier_ghz=3.5, anchor_ms=2.5)

AR(1) fading correlation per ms for a UE speed: the Jakes correlation J0(2 pi f_D anchor) over one legacy
UL slot spacing (2.5 ms), spread geometrically over its milliseconds. f_D = v f_c / c. 3 m/s at 3.5 GHz gives
0.9697 per ms (J0 = 0.926 per 2.5 ms), close to the default 0.93 per 2.5 ms. Monotone non-increasing in speed:
from the first zero of J0 on (2 pi f_D anchor >= 2.405, about 13.1 m/s at 3.5 GHz) consecutive 2.5 ms samples
are treated as uncorrelated (rho = 0), and the oscillating tail of J0 past that zero is not used.

rbg_size_38214

rbg_size_38214(n_prb, config=1)

RBG size P in PRBs for a bandwidth part of n_prb PRBs (TS 38.214 Table 5.1.2.2.1-1, configuration 1 or 2).