Skip to content

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

reset(env_ids=None)

Re-initialize env_ids (None = all): frame buffers, MAC/level state, clock and radio. In place.

submit

submit(t, requests, snr_db=None)

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

step(t, x, cur_hid=None)

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

attach_radio(radio)

Use an external Radio for step(t, poses); reset(env_ids) then resets its rows too.

add_frames

add_frames(t, send, det, hid, snr_db)

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

NREngine(E, R, device, cfg: NRConfig, seed=None)

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.

clock property

clock

reset

reset(env_ids=None)

Re-initialize env_ids (None = all): queues, HARQ, SR/BSR, OLLA, PF, CSI, fading, radio and clock.

submit

submit(t, requests, snr_db=None, *, tag=None, priority=None, deadline_ms=None)

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

add_dl_frames(t, nbytes, cls=None)

Downlink messages of nbytes [E,R] (0 = none) at t (needs config.dl).

attach_radio

attach_radio(radio)

Use an external RadioMC (C = 1) for step(t, poses); reset(env_ids) then resets its rows too.

set_sinr_hook

set_sinr_hook(fn, direction='ul')

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.

add_frames

add_frames(t, send, det, hid, snr_db)

Legacy wrapper (NetSlot API).