Motion sampling

How often Radar evaluates a moving world inside one frame, what each Motion kind trades, and how to read completeness flags on a Result.

A radar frame lasts milliseconds, and the world can move during it. Doppler and micro-Doppler come from that motion, so Radar has to decide how many times to evaluate the world inside each frame. The motion argument of trace, simulate and stream sets that choice. This page explains the five choices and what they cost.

The five kinds#

Build a Motion with one of its constructors. The default is Motion.auto().

KindEvaluates the worldUse it for
Motion.auto()Like adc if anything moves or phase noise is set, otherwise like staticThe default. Correct for every session
Motion.static()Once per frameA world that does not move. Refused if anything moves
Motion.chirp()Once per chirp, symbol or pulse (stop and hop)A faster approximation when motion within one chirp is negligible
Motion.adc()At every ADC sample instantThe exhaustive reference
Motion.adaptive(...)At error-controlled probe instants, interpolated in betweenLong FMCW sequences of a moving world. FMCW only

"Anything moves" means any of: a structure trajectory or deformation in a DynamicScene, an endpoint trajectory, or a PointTargets trajectory. Oscillator phase noise (Noise(phase_density=...)) also selects per-ADC sampling, because the delayed phase difference must be placed at ADC times.

When the world is evaluated#

For a TDM-MIMO FMCW frame starting at frame_time, sample sample of chirp chirp from transmitter tx is taken at:

frame_time + (chirp · num_tx + tx) · chirp_period + adc_start + sample · sample_period

Motion.adc() evaluates the world at every one of these instants: chirps · num_tx · samples evaluations per frame. Motion.chirp() freezes the geometry within each chirp slot, OFDM refreshes once per symbol and pulsed once per pulse. Motion.static() evaluates once per frame and extrapolates the delay linearly from that observation, so it gives no Doppler for a moving world. That is why it is refused when anything moves.

result.sample_times_s records the instants each frame used, and result.motion_sampling names the kind that ran after auto was resolved.

The default is slow for a moving world

Because Motion.auto() resolves to adc when anything moves, a moving scene evaluates the world at every ADC instant. For the 3-TX, 128-chirp, 256-sample frame in Moving target and Doppler, that is 98304 evaluations and took 1086 s on an RTX 5080, against 4.4 s with Motion.chirp(). Pass Motion.chirp() or Motion.adaptive() explicitly for moving scenes unless you need the exact reference.

Adaptive sampling#

Motion.adaptive(...) evaluates the world at a small number of probe instants and interpolates the path delays and weights between them. It splits the frame into intervals and accepts an interval only if the interpolation error is inside both tolerances:

ArgumentDefaultMeaning
phase_error0.02Maximum per-path phase error, radians
relative_amplitude_error0.02Maximum relative amplitude error
max_interval0.002Longest interval one interpolation may span, seconds
nodes2Interpolation nodes per interval; 2 is linear. Each interval is probed at 2 · (nodes - 1) + 1 instants
max_evaluations8192Probe budget per frame. Exhausting it raises RuntimeError
batch_observations256How many probe instants are evaluated per batch

On the repository's measured scenes, adaptive sampling is 3 to 64 times faster than Motion.adc(). Set the tolerance to the IQ error you are willing to accept: the measured IQ error relative to the tested phase residual ranged from 0.44 to 1.19.

Warning

Adaptive sampling checks its error only at the probes. A path that appears and disappears between two probes, or motion that repeats exactly at the probe spacing, is invisible to it. Use Motion.adc() as the reference when you validate a new scene.

Topology rediscovery#

At each dynamic observation, Radar asks Channel to discover the path topology from scratch. This catches paths that appear or disappear. rediscover_every_frames=n discovers only every n frames and re-evaluates the known paths in between. It is faster, but a path born between discoveries is missed, so the result reports path_set_complete=False unless structure motion already forces discovery.

world says what happens to the discovered paths when the geometry moves:

  • "frozen_world" (default): a geometry change retires every discovered path, and Radar discovers again.
  • "fixed_winner_replay": the discovered set of paths is held fixed and each path is re-evaluated on the moved geometry. A path that stops existing is published as an exact zero, but a path that starts existing is not published at all. Use it only when you know the moving geometry cannot create new paths, or combine it with rediscover_every_frames. A change of topology, material or assignment is refused in both modes.

Reading the completeness flags#

Every Result and Paths carries two separate flags:

  • path_set_complete: no path birth can have been missed, because every observation was evaluated with discovery, or the candidate family was certified complete for all time.
  • motion_sampling_exhaustive: no observation's transport was interpolated between probes.

An adaptive run reports motion_sampling_exhaustive=False whenever it interpolated between probes. It has path_set_complete=True only when the candidate path family was certified complete for the whole frame; a scene with reflections usually reports False. That is the trade adaptive sampling makes. For adaptive runs, adaptive_diagnostics holds per-frame probe counts and errors.