Skip to content

Isaac Lab layer

isaac_net.isaac puts a network into an Isaac Lab task. Only rigid_positions_local and the mdp event term touch Isaac Lab objects, and nothing in the package imports Isaac Lab at import time, so NetModule, MessageHistory and the mixin hooks also run on a CPU without Isaac (Tutorial 04 does exactly that).

Name Role
NetModule the network of one environment batch, on any make_engine level, plus the Isaac radio, per-message tags and freshness outputs
TrafficRequest messages per robot for one control step, with an optional per-message tag
NetEnvMixin four hook calls that wire a NetModule into a DirectRLEnv
MessageHistory the receiver's delayed view of per-robot payloads
IsaacRadio, ParamRanges poses to SNR with per-environment parameters, several gNBs and line-of-sight blockage
mdp.randomize_network an event term that redraws the per-environment radio parameters (network domain randomization)
net_features the compact [E, R, 4] network observation

Where the calls go in a DirectRLEnv

DirectRLEnv.step in Isaac Lab 3.0 calls _pre_physics_step, then the physics substeps, then _get_dones, _get_rewards, _reset_idx for finished environments, and finally _get_observations. The mixin's calls fit this order:

Hook Call Why there
_setup_scene self.net_setup(level, R, config, backend, **kwargs) builds the NetModule once for all environments
_pre_physics_step (task code) read the start-of-step pose and decide what to send messages are captured at the start of the step
_get_dones out = self.net_step(poses_end, send, tag, cur_tag) the first hook after physics: it sees the end-of-step poses and the queues before any reset
_reset_idx self.net_reset(env_ids) after super()._reset_idx(env_ids) partial reset of the finished environments only
_get_observations self.net_obs() [E, R, 4] AoI, SNR, queued messages, delivered; zeros for environments that reset this step

net_setup("off") removes the network: net_step returns None and net_obs returns zeros of the same shape, which is the ideal-link baseline. A multi-cell config needs the engine's own radio, net_setup(..., radio="engine").

NetModule outputs

NetModule.step returns a dict whose keys differ from the core engine's in a few places, because it is written for task code:

Key Shape Meaning
delivered [E, R] bool at least one message of the robot was delivered this step
newest_cap [E, R] long newest capture step delivered this step, -1 if none
last_cap [E, R] long newest capture step delivered this episode (0 means the state at reset)
aoi_s [E, R] float age of that information at the end of the step, in seconds
queue_len, queue_bytes [E, R] queue state after the step
sinr_db, serving, blocked [E, R] radio of this step
tag_delivered [E] bool a message tagged with the environment's current tag arrived (only if cur_tag is given)
msg_delivered, timed_out, cap, cls, delay_s [E, R, F] per message slot, as in the engine; delay_s in seconds
t [E] long the clock value of this step

NetModule

NetModule(level='L2-legacy', num_envs: int = None, num_robots: int = None, device='cuda', config: Optional[NRConfig] = None, backend: str = 'reference', *, isaac: Optional[IsaacNetCfg] = None, ranges: Optional[ParamRanges] = None, params: Optional[dict] = None, seed: Optional[int] = None, inject: bool = False, strict: bool = False, **isaac_overrides)

Network in the loop of a batch of E envs x R robots (see the module docstring).

clock property

clock: Tensor

[E] per-env episode clock (control steps since that env's reset).

reset

reset(env_ids=None)

Partial reset of env_ids (index tensor, bool mask, list or None = all): queues, MAC, fading, clock,
the shadowing field, the freshness state and the observation features. Radio parameters stay, unless
IsaacNetCfg.dr_mode is "reset", which redraws them. Other envs are bitwise unaffected.

submit

submit(t, req: TrafficRequest)

Messages captured at the start of this control step (t is ignored: per-env clocks). Call before step.

step

step(t, poses: Tensor, cur_tag: Optional[Tensor] = None, blocked_fn=None, blockers: Optional[Tensor] = None) -> dict

Advance every env by one control step (t is ignored: per-env clocks).

poses [E,R,3] (or [E,R,2]) env-local positions at the END of this control step.
cur_tag [E] long: the env's current tag (-1 = none) for tag_delivered.
blocked_fn(poses [E,R,3]) -> [E,R,G] bool line-of-sight blockage, evaluated per pose chunk (the per-env gNB
positions are net.radio.gnb_env [E,G,3]). Ignored with IsaacNetCfg.blockage = False. With radio="engine"
it becomes the engine radio's LOS source when NRConfig.los_source="callback" (multi-cell scene blockage;
the poses are in the radio frame, the gNBs at the config's cells), else it is ignored with a warning.
blockers [E,M,3] (x, y, class): extra dynamic blockers (humans, vehicles) as TR 38.901 model-B screens, for
radio="engine" at level L2 with NRConfig(blockage=True, blockage_model="screen").
Returns a dict:
  delivered [E,R] bool      at least one message of the robot was delivered this step
  newest_cap [E,R] long     newest capture step delivered this step (-1 if none), env clock
  last_cap [E,R] long       newest capture step delivered this episode (0 = the state at reset)
  aoi_s [E,R] float         age of that information at the end of the step, in seconds
  queue_len, queue_bytes    FIFO state after the step
  sinr_db [E,R], rsrp_dbm [E,R], serving [E,R], blocked [E,R], los [E,R]   radio of this step (rsrp =
                            SINR + the env's noise floor: the received power when there is no
                            interference; los = the engine radio's LOS state of the serving link, or
                            ~blocked when the radio has none)
  tag_delivered [E] bool    if cur_tag is given
  access_state [E,R] long, access_sleep_frac [E,R], rach_attempts [E,R], rlf [E,R] bool
                            passed through from the NR engine when it reports them (core/access.py:
                            NRConfig rach / drx; core/radio.py: rlf with several cells)
  msg_delivered, timed_out [E,R,F] bool, cap, cls [E,R,F] long, delay_s [E,R,F] float (NaN if not
  delivered), t [E] long    per message slot as queued before the step, from the engine

set_params

set_params(env_ids=None, **values)

Per-env radio parameters (RADIO_PARAMS, gnb_offset_m) of env_ids. Never touched by reset().

sample_params

sample_params(env_ids=None, ranges: Optional[dict] = None)

Uniform draw of radio parameters within ranges ({name: (lo, hi)}, default: the module's ParamRanges).

queued

queued() -> torch.Tensor

TrafficRequest dataclass

TrafficRequest(send: Tensor, tag: Optional[Tensor] = None)

Messages each robot enqueues this control step.

send: [E,R] long, 0 = nothing, c >= 1 = one message of traffic class c (size config.msg_sizes[c-1]).
tag:  optional [E,R] long per-message label (-1 = none), e.g. the hazard id if the frame detects it.

NetEnvMixin

net_setup

net_setup(level, num_robots: Optional[int] = None, config=None, backend: str = 'reference', isaac: Optional[IsaacNetCfg] = None, markers=None, **kwargs)

Build the network for this env batch; call inside _setup_scene.

level: None or "off" (ideal link, no network features), or a make_engine level ("L0", "L0DR", "L1",
"L2-legacy", "L2", ...). config: NRConfig. backend: "reference" | "eager" | "graph" | "compile" |
"triton". isaac: IsaacNetCfg. markers: a NetMarkersCfg (or True) for viewport overlays, inert when
headless (isaac/markers.py). kwargs go to NetModule (params, seed, strict, ranges, and IsaacNetCfg
fields as shortcuts). A NetConfig (deprecated) is also accepted as level.

net_step

net_step(poses_end: Optional[Tensor] = None, send: Optional[Tensor] = None, tag: Optional[Tensor] = None, cur_tag: Optional[Tensor] = None, blocked_fn=None) -> Optional[dict]

Submit this step's messages (captured at the start-of-step pose) and advance the network by one env
control step with the END-of-step poses [E,R,3] (None: read from IsaacNetCfg.pose_asset). Returns the
NetModule output dict, or None without a network.

net_reset

net_reset(env_ids)

net_obs

net_obs() -> torch.Tensor

[E,R,obs_dim] the IsaacNetCfg.obs_features of the last network step; zeros without a network, before the
first step, and for envs that reset since (their last output belongs to the previous episode).

rigid_positions_local

rigid_positions_local(collection, env_origins: Tensor, body_ids=None) -> torch.Tensor

[E,R,3] env-local body positions of a RigidObjectCollection, Articulation or RigidObject (3.0 ProxyArray or
2.x torch tensor). body_ids selects bodies (None = all).

MessageHistory

MessageHistory(num_envs: int, num_robots: int, dim: int, history_len: int, device='cuda')

Ring buffer of per-robot payloads indexed by env-clock capture step (the receiver's delayed view).

push(t_env, data) stores data [E,R,D] captured at each env's own clock (the start-of-step pose). After
net.step, update(out["newest_cap"]) replaces the receiver's copy for robots whose newest delivered capture is
fresher than what it holds; others keep the last payload (hold-last on loss). history_len must exceed
config.timeout_steps so that every deliverable capture is still stored.

push

push(t_env: Tensor, data: Tensor)

update

update(newest_cap: Tensor) -> torch.Tensor

reset

reset(env_ids: Tensor, init: Tensor)

init [n,R,D]: the receiver is told the true state at reset (last_cap = 0 in NetModule).

net_features

net_features(out: dict, step_dt: float, depth: int = 16) -> torch.Tensor

Deprecated: the default observation [E,R,4] (AoI, SNR, queued frames, delivered this step) with the one
normalization of isaac/obs.py. Use NetModule.obs() with IsaacNetCfg.obs_features instead.

IsaacRadio

IsaacRadio(E: int, device, gnb_pos, ranges: ParamRanges, shadow_modes: int = 8, seed: Optional[int] = None)

Per-env radio state and parameters for E envs; snr_db(pos, blocked) -> [E,R,G].

reset

reset(env_ids=None)

Redraw the shadowing field of env_ids (None = all) in place, as core Radio.reset. Parameters stay.

set_params

set_params(env_ids=None, **values)

Overwrite per-env parameters of env_ids. A value is a scalar or a [len(env_ids)] tensor.

sample_params

sample_params(env_ids=None, ranges: Optional[dict] = None)

Uniform draw within ranges ({name: (lo, hi)}, default self.ranges) for env_ids, from self.gen.

params

params() -> dict

snr_db

snr_db(pos: Tensor, blocked: Optional[Tensor] = None) -> torch.Tensor

Full-power single-subband uplink SNR per gNB in dB. pos [E,R,2|3] env-local (z = 0 if 2-D).
blocked [E,R,G] bool adds blockage_db where the line of sight is blocked. Returns [E,R,G].

ParamRanges dataclass

ParamRanges(p_tx_dbm: tuple = (23.0, 23.0), noise_dbm: tuple = (-90.0, -90.0), pl_const_db: tuple = (40.0, 40.0), pl_exp: tuple = (3.5, 3.5), shadow_sigma_db: tuple = (6.0, 6.0), blockage_db: tuple = (20.0, 20.0))

Per-env radio parameters: (lo, hi) of a uniform draw. The midpoint is the value before any randomization.

The defaults are the legacy single-cell radio (NRConfig defaults); ParamRanges.from_config(cfg) takes them
from a config. noise_dbm is noise plus interference per snr_ref_prbs-PRB subband.

randomize_network

randomize_network(env, env_ids: Tensor | None, ranges: dict[str, tuple] | None = None)

Resample per-env radio parameters uniformly within ranges for env_ids.

Parameters (isaac.radio.RADIO_PARAMS and gnb_offset_m): p_tx_dbm, noise_dbm, pl_const_db, pl_exp,
shadow_sigma_db, blockage_db, gnb_offset_m (per-env x/y offset of every gNB). ranges=None takes the radio keys
of the module's IsaacNetCfg.dr_ranges.
Example: EventTermCfg(func=randomize_network, mode="reset",
                      params={"ranges": {"pl_exp": (2.8, 4.0), "shadow_sigma_db": (3.0, 8.0)}})
In mode "reset" it runs inside DirectRLEnv._reset_idx (super) BEFORE net_reset; net_reset never touches
parameters unless IsaacNetCfg.dr_mode is "reset", so the drawn values hold for the new episode. Delay and loss
of L0 / L0DR are engine parameters: set them through IsaacNetCfg.dr_ranges (NRConfig dr_* / l0_* fields).

NetConfig dataclass

NetConfig(num_envs: int, num_robots: int, device: str = 'cuda', rung: Optional[str] = None, backend: Optional[str] = None, step_dt: Optional[float] = None, slot_dt: Optional[float] = None, pose_chunks: Optional[int] = None, frame_depth: Optional[int] = None, msg_sizes: Optional[Sequence[float]] = None, timeout_steps: Optional[int] = None, gnb_pos: Optional[Sequence[Sequence[float]]] = None, config: Optional[NRConfig] = None, isaac: Optional[IsaacNetCfg] = None)

Deprecated alias of the demo's configuration; it has no defaults of its own. Pass level, an NRConfig and an
IsaacNetCfg to NetModule instead. Every field left at None comes from config (an NRConfig, default
NRConfig()) or from IsaacNetCfg: rung None = "L2-legacy" ("L2" of the demo is the slot-level NetSlot, i.e.
level "L2-legacy"), backend None = "reference" ("ref" also means "reference"), step_dt = control_step_ms,
frame_depth = frame_buffer, msg_sizes and timeout_steps from the NRConfig, pose_chunks and gnb_pos from
IsaacNetCfg. to_kwargs() maps the fields onto NetModule(level, E, R, device, config, ...).