Engine¶
make_engine(level, E, R, device, config, backend) builds the network of E environments with R robots each at one fidelity level. Every engine it returns has the same API, the engine contract below, so a task switches fidelity by changing the first argument.
import torch
from isaac_net import NRConfig, Requests, make_engine
net = make_engine("L2-legacy", E=256, R=16, device="cuda", config=NRConfig(), backend="graph", seed=0)
net.submit(None, Requests(send)) # send [E,R] long: 0 nothing, c >= 1 one message of class c
out = net.step(None, poses) # poses [E,R,2|3] in metres, or an SNR [E,R] in dB
net.reset(done_ids) # partial reset of the envs whose episodes ended
The engine contract¶
| Call | Meaning |
|---|---|
reset(env_ids=None) |
Re-initialize env_ids (all if None; an index tensor, a list or a bool mask [E]): queues, MAC and HARQ state, link adaptation, fading, radio and clock. Other environments stay bit for bit unaffected, and the reset's random draws come from the engine generator. |
submit(t, requests, snr_db=None) |
Enqueue new messages captured at t. requests is a Requests or a send [E, R] tensor. snr_db [E, R] is recorded as a message feature (default: the SNR of the previous step). Returns accepted [E, R] bool. On L2, the keyword arguments tag=, priority= and deadline_ms= attach per-message extras. |
step(t, poses_or_snr) |
Advance every environment from t to t + 1 and return the output dict below. Positions [E, R, 2] or [E, R, 3] go through the engine's radio; an [E, R] tensor is taken as the SNR in dB. |
clock |
[E] long, control steps since each environment's last reset. |
queued() |
[E, R] long, messages in each robot's queue. |
t is None in almost all code, which means "each environment's own clock". The prototype levels also accept an int (the same step for every environment) or an [E] tensor. The NR engine accepts an explicit t only when it equals its clock, because it cannot jump in time.
The legacy calls add_frames(t, send, det, hid, snr_db) and step(t, snr_db, cur_hid) -> (newest, det_env) remain as thin wrappers, so code written for the prototype keeps working.
Step outputs¶
step returns a dict. Per-message entries refer to the robot's F message slots as queued before the step (F = NRConfig.frame_buffer, 16 by default).
| Key | Shape, type | Meaning |
|---|---|---|
delivered |
[E, R, F] bool |
the message in this slot was delivered during the step |
timed_out |
[E, R, F] bool |
the message in this slot hit its deadline (timeout_steps) and was dropped |
cap, cls |
[E, R, F] long |
capture step and traffic class of the slot (-1 and 0 if empty) |
delay |
[E, R, F] float |
delivery time minus capture step, in control steps; NaN if not delivered |
newest |
[E, R] long |
newest capture step delivered in this step, -1 if none |
det_env |
[E] bool |
a message carrying the environment's current event id (Requests.hid) was delivered |
queue_len |
[E, R] long |
messages queued after the step |
queue_bytes |
[E, R] float |
bytes still queued after the step |
sinr_db |
[E, R] float |
wideband SNR or SINR used for this step |
t |
[E] long |
the clock value of this step; the clock is now t + 1 |
Some engines add entries:
| Key | Engines | Meaning |
|---|---|---|
serving_cell |
L2, multi-cell L2-legacy |
serving cell of each robot [E, R] (0 with one cell) |
dropped |
L2 |
[E, R, F] messages lost under RLC unacknowledged mode (harq_fail="drop") and resolved this step |
dl_newest, dl_queue_len |
L2 with NRConfig(dl=True) |
downlink counterparts of newest and queue_len |
arrival, arrival_slot, tag, priority, bytes, deadline_miss |
L2 with traffic models or submit extras |
per message [E, R, F]: arrival time in the env clock including the in-step offset, its slot, the extras, bytes on the air, and whether the deadline was missed; delay then counts from the arrival slot |
gen_accepted, gen_bytes |
L2 with traffic models |
[E, R] generated messages and bytes accepted this step |
Capture steps in every output are in the environment's own clock, so after a reset they restart at 0.
Constants¶
| Name | Value |
|---|---|
LEVELS |
every level make_engine accepts: SIM_LEVELS + SURROGATE_LEVELS + BOUND_LEVELS |
SIM_LEVELS |
("L0", "L0DR", "L05", "L05Q", "L1", "L2", "L2-legacy") |
SURROGATE_LEVELS, BOUND_LEVELS |
("TR", "GE", "QA", "NN") and ("ORACLE", "NOCOMM") |
BACKENDS |
("reference", "eager", "graph", "compile", "triton") |
FAST_BACKENDS |
BACKENDS without "reference" |
Which backend is available for which level is listed on Fidelity levels.
make_engine
¶
make_engine(level, E, R, device='cpu', config: NRConfig | None = None, backend='reference', *, sizes=None, params=None, seed=None, inject=False, strict=False)
Build the network engine of fidelity level for E envs x R robots.
config: NRConfig shared by every module (default NRConfig()); the prototype levels read only its application
fields (frame_buffer, timeout_steps, control_step_ms -> UL slots per step, msg_sizes) and rng / seed.
sizes: override of config.msg_sizes. params: fitted parameters of L0 ({"mu", "sig", "p"}, or
{"q", "p"} for an i.i.d. delay from an empirical marginal: q = delay quantiles or sorted sample in control
steps, drawn by inverted CDF) and L05 / L05Q
({"q", "pdrop"}); for TR / GE / QA / NN a fit file path, a fit-file dict or the level's own dict (see
levels.load_level_params). seed: overrides config.seed. With config.rng = "engine" (default) every draw of
the prototype, surrogate and bound levels comes from the engine's streams seeded by it (proto/rng.py);
with "global", reset draws use the engine generator and stepping draws the global torch RNG. The NR engine
(L2) keys its slot draws and, with "engine", its radio (RadioMC) draws by (seed, env id, episode) as well;
only traffic models (TrafficGen) still use one generator per engine.
backend: "reference" (default), a fast backend (BACKENDS), or "auto": resolve_backend picks triton, graph or
reference for this level, config and device and logs its choice (INFO, logger "isaac_net").
inject: fast backends only, take the per-slot random draws from set_noise(...) (equivalence tests).
strict: fields set away from their defaults that this level ignores (config.unused_fields(level)): False (default)
emits one UnusedFieldsWarning per level and set of such fields per process, True raises ValueError, None skips
the check silently (the behavior before the warning).
L0, L0DR and L1 without params take them from the config (l0_*, dr_*, l1_eta; defaults = earlier behavior).
config.edge (an EdgeConfig) wraps the engine in core.edge.EdgeLoop: same API, plus the edge-loop step keys.
The prototype engine base¶
The prototype levels (L0 to L1, L2-legacy), the surrogates and the bounds all derive from NetBase, whose docstrings define the contract calls in detail.
NetBase
¶
NetBase(E, R, device, sizes, seed=None, fb=F, timeout=TIMEOUT, ul_per_step=UL_PER_STEP, rng='global')
reset
¶
Re-initialize env_ids (None = all): frame buffers, MAC/level state, clock and radio. In place.
submit
¶
Enqueue new messages at capture time t (None = engine clock).
requests: Requests or a send tensor [E,R]. snr_db [E,R] is the SNR recorded as a frame feature
(used by the L05/L05Q lookup); None uses the SNR of the previous step. Returns accepted [E,R] bool
(False where the robot sent nothing or its FIFO was full).
step
¶
Advance [t, t+1) (t = None uses the engine clock). x: SNR [E,R] dB or positions [E,R,2|3].
Legacy form step(t, snr_db, cur_hid) returns (newest delivered capture step [E,R], detection
delivered [E]). Without cur_hid it returns a dict:
delivered [E,R,F] bool message delivered during this step (message slots as queued before the step)
timed_out [E,R,F] bool message dropped at its deadline this step
cap, cls [E,R,F] capture step and traffic class of those message slots (-1 / 0 = empty)
delay [E,R,F] float delivery time - capture step, in control steps; NaN if not delivered
newest [E,R] long newest delivered capture step this step, -1 if none
det_env [E] bool a detection of the env's current hazard (hid of the last submit) arrived
queue_len [E,R] long, queue_bytes [E,R] float: FIFO state after the step
sinr_db [E,R] wideband SNR used for this step
t [E] long the clock value of this step (the engine clock is now t + 1)
attach_radio
¶
Use an external Radio for step(t, poses); reset(env_ids) then resets its rows too.
add_frames
¶
Legacy wrapper: send [E,R] in {0,1,2}; det [E,R] bool; hid [E] current hazard id; snr_db [E,R].
NREngine¶
make_engine("L2", ...) returns an NREngine, which wraps the configurable NR engine NRNet in the contract API. Attributes it does not define itself, such as the per-direction MAC objects ul and dl, are forwarded to the wrapped NRNet.
NREngine
¶
Contract API over NRNet (level "L2").
NRNet simulates continuous physical time with one global control-step clock. NREngine keeps that clock in
self.T and gives every env an episode clock clock[e] = T - epoch[e] that reset(env_ids) zeroes, so its
outputs use the same per-env clock as the prototype levels (capture steps, newest, t). HARQ, SR and CQI
timers stay in global slots; reset(env_ids) clears them for those envs, which makes the reset exact.
t arguments: None uses the engine clock (recommended). An int or an [E] tensor must equal the engine clock;
the NR engine cannot jump in time. After a partial reset an int can no longer match every env: pass None.
reset
¶
Re-initialize env_ids (None = all): queues, HARQ, SR/BSR, OLLA, PF, CSI, fading, radio and clock.
submit
¶
Enqueue new messages at capture time t (None = engine clock). requests: Requests or send [E,R].
snr_db [E,R] is recorded as a frame feature (default: the SNR of the previous step). Returns accepted.
tag / priority ([E,R] long or int) and deadline_ms ([E,R] float or float) are optional per-message extras
that the queue carries and step() reports (tag, priority, deadline_miss).
step
¶
step(t, x=None, cur_hid=None, *, snr_db=None, dl_snr_db=None, pathgain_db=None, vel=None, triggers=None, blockers=None)
Advance [t, t+1). x: SNR [E,R] in dB, or poses [E,R,2|3] (through the engine's radio); snr_db=
takes a per-subband SNR [E,R,S]. With several cells (config.n_cells > 1) x must be poses, or pass
pathgain_db= [E,R,C] (large-scale gain of every robot-cell link, dB). vel [E,R,2|3] (m/s): robot velocities
for config.fading_doppler="per_robot" (default: from consecutive poses). Legacy form step(t, x, cur_hid)
returns (newest, det_env). Without cur_hid it returns the dict of the module docstring plus, for this
engine:
dropped [E,R,F] bool lost under RLC UM (harq_fail="drop"), or on a handover with ho_rlc="flush", and
resolved this step
serving_cell [E,R] serving cell (0 with one cell)
dl_newest, dl_queue_len [E,R] when config.dl
With several cells sinr_db is the serving-link SINR against the gNB's latest N+I estimate.
With traffic models (NRConfig.traffic) the step first generates this step's messages; triggers= feeds the
event models (a mask [E,R] / [E], or {name: mask}). With traffic models or submit extras it also returns,
per frame [E,R,F]: arrival (env clock incl. the in-step offset), arrival_slot, tag, priority, bytes (on the
air), deadline_miss; delay is then measured from the arrival slot. gen_accepted / gen_bytes [E,R] count
the generated messages accepted this step.
blockers [E,M,3] (x, y, class): extra dynamic blockers of this step for blockage_model="screen" (humans,
vehicles; class indexes NRConfig.blocker_size_m, \< 0 = empty slot); needs poses. With the obstacle stack on
(los_source != "stochastic" or blockage) and poses, the dict also has los / blocked [E,R] bool: LOS state
and dynamic blockage of the serving link (docs/obstacles.md).
add_dl_frames
¶
Downlink messages of nbytes [E,R] (0 = none) at t (needs config.dl).
attach_radio
¶
Use an external RadioMC (C = 1) for step(t, poses); reset(env_ids) then resets its rows too.
set_sinr_hook
¶
fn(g, dir, won [E,R,S], n_prb [E,R], sinr [E,R,S]) -> sinr [E,R,S] is called between allocation and
decoding in every data slot of that direction (won: RBGs of the robots that transmit). With several cells
the engine's own inter-cell interference runs first and fn gets its result.