Skip to content

Radio map gradients w.r.t. transmitter position are silently zero (evaluated loop mode) #69

Description

@Speaknomadic2025

Summary

On sionna-rt 2.0.1, differentiating a planar radio map w.r.t. a transmitter position produces exactly-zero gradients with no error or warning: dr.grad_enabled(rm.path_gain) is True, dr.backward() completes normally, but dr.grad(position) comes back [0, 0, 0]. Gradients w.r.t. the same transmitter's orientation are finite and nonzero in the very same backward pass, so the AD chain through RadioMapSolver is only partially connected.

Because the default "symbolic" loop mode is documented as non-differentiable, a user who switches to "evaluated" and then sees grad_enabled == True has every reason to trust the zeros — which makes this easy to misread as a genuinely flat objective rather than a broken gradient.

Environment

sionna / sionna-rt 2.0.1 (latest release; planar_radio_map.py unchanged on main)
drjit 1.3.1
mitsuba 3.8.0, variant cuda_ad_mono_polarized
Python 3.12.3
OS / arch Ubuntu 24.04.4 LTS, aarch64 (DGX Spark, GB10)
Driver / CUDA 580.159.03 / 13.0

Minimal reproduction

# Repro: RadioMapSolver gradients w.r.t. TX position silently zero (orientation grads flow)
# sionna-rt 2.0.1 | drjit 1.3.1 | mitsuba 3.8.0 | variant cuda_ad_mono_polarized
import drjit as dr
import mitsuba as mi
import sionna.rt as rt
from sionna.rt import load_scene, PlanarArray, Transmitter, RadioMapSolver

scene = load_scene(rt.scene.simple_street_canyon)
scene.frequency = 3.5e9
scene.tx_array = PlanarArray(num_rows=1, num_cols=1, pattern="tr38901", polarization="V")
scene.rx_array = PlanarArray(num_rows=1, num_cols=1, pattern="iso", polarization="V")

tx = Transmitter("tx", position=mi.Point3f(0.0, 0.0, 20.0))
scene.add(tx)

pos = mi.Point3f(0.0, 0.0, 20.0)
ori = mi.Point3f(0.3, -0.1, 0.0)
dr.enable_grad(pos)
dr.enable_grad(ori)
tx.position = pos
tx.orientation = ori

solver = RadioMapSolver()
solver.loop_mode = "evaluated"  # documented requirement for autodiff
rm = solver(scene, max_depth=2, cell_size=(8.0, 8.0), samples_per_tx=100_000)

print("grad_enabled(rm.path_gain):", dr.grad_enabled(rm.path_gain))
loss = dr.mean(dr.ravel(rm.path_gain), axis=None)
print("loss:", loss)
dr.backward(loss)
print("grad wrt position:   ", dr.grad(pos))
print("grad wrt orientation:", dr.grad(ori))

Output on 2.0.1:

grad_enabled(rm.path_gain): True
loss: [6.86599e-09]
grad wrt position:    [[0, 0, 0]]
grad wrt orientation: [[-3.40565e-09, 1.67916e-08, 8.25789e-11]]

Expected vs. actual

Expected: finite, nonzero d(loss)/d(tx.position) — path gain depends continuously on TX position through propagation distance (free-space 1/d² at minimum), just as it depends on orientation through the antenna pattern.

Actual: position adjoints are structurally zero (exactly [0, 0, 0], not small), while orientation adjoints of magnitude ~1e-9 to 1e-8 flow correctly in the same pass. No error, no warning.

With the default loop_mode = "symbolic", grad_enabled(rm.path_gain) is False and dr.backward() raises — that case is documented. The silent case above is specific to "evaluated" mode.

Notes on the suspected cause

  • Orientation enters the computation through the antenna-pattern weighting of the e-fields; position enters only through ray-spawn geometry and path lengths. The asymmetry suggests geometric quantities are consumed detached somewhere in the radio-map solver loop or the PlanarRadioMap.add() accumulation (src/sionna/rt/radio_map_solvers/planar_radio_map.py, ~lines 273–305), so parameters acting purely through geometry receive no adjoint.
  • We ruled out the dr.scatter_reduce call at line 304 in isolation: a standalone Dr.Jit test with the same mixed mi.UInt/mi.Int index arithmetic backpropagates correctly.
  • Radio-map differentiability appears to be an intended feature (e.g. the v1.1.0 release fixed NaN radio-map gradients, Gradient-Based optimization using Radiomaps sionna#886). If position gradients through radio maps are a known limitation instead, a warning or a docs note would prevent silent failures.

Closest existing reports we could find (open + closed, both this repo and NVlabs/sionna): #66 / #37 / #6 concern the PathSolver's local-memory writes and fail loudly; NVlabs/sionna#440 is the legacy TF API. None cover this silent radio-map case.

Impact

Gradient-based transmitter placement over radio maps is currently unusable: on our coverage-optimization pipeline (DGX Spark GB10) we had to fall back to SPSA numerical gradients, at roughly 2 full radio-map evaluations per optimizer step instead of one differentiable pass.

Happy to test patches or provide more diagnostics.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions