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.
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)isTrue,dr.backward()completes normally, butdr.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 throughRadioMapSolveris only partially connected.Because the default
"symbolic"loop mode is documented as non-differentiable, a user who switches to"evaluated"and then seesgrad_enabled == Truehas every reason to trust the zeros — which makes this easy to misread as a genuinely flat objective rather than a broken gradient.Environment
planar_radio_map.pyunchanged onmain)cuda_ad_mono_polarizedMinimal reproduction
Output on 2.0.1:
Expected vs. actual
Expected: finite, nonzero
d(loss)/d(tx.position)— path gain depends continuously on TX position through propagation distance (free-space1/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)isFalseanddr.backward()raises — that case is documented. The silent case above is specific to"evaluated"mode.Notes on the suspected cause
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.dr.scatter_reducecall at line 304 in isolation: a standalone Dr.Jit test with the same mixedmi.UInt/mi.Intindex arithmetic backpropagates correctly.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.