Simulate moving scenes
Move targets with a trajectory, move walls and objects with a Core DynamicScene, move antenna phase centres, and choose the motion sampling.
Doppler, micro-Doppler and range migration come from motion during a frame. This page shows the four ways to make something move and how to pick the motion sampling. For the model behind it, read Motion sampling.
Move a target#
Give PointTargets a trajectory: a callable that takes a time in seconds and returns the target positions at that time as an (S, 3) tensor in metres, in the same order as positions.
import torch
from witwin.radar import Motion, PointTargets
speed = 1.0 # m/s toward the radar, which looks along -z
def approach(t):
return torch.tensor([[0.0, 0.0, -3.0 + speed * t]], device=radar.device)
targets = PointTargets(positions=[(0.0, 0.0, -3.0)], rcs=1.0, trajectory=approach)
result = radar.simulate(scene, targets, times=(0.0,), reflections=0)
print(result.motion_sampling) # "adc": auto resolved to per-ADC sampling
rd = result.frame(0).range_doppler()
power = rd.data.abs().sum(dim=(0, 1))
doppler_bin, range_bin = divmod(int(power.argmax()), power.shape[1])
print(float(rd.doppler_axis[doppler_bin]), float(rd.range_axis[range_bin])) # about +1 m/s, about 3 mThe trajectory must return positions, not velocities. Rotation and articulation must move the points; an angular-velocity label alone moves nothing. Radar never differentiates positions into a velocity: the delay rate comes from the propagation solve at each instant.
Move a structure#
Wrap the static Scene in a Core DynamicScene and give the structure a trajectory. LinearTrajectory moves a structure from its authored position by origin + velocity · t, with optional rotation and angular_velocity.
import torch
from witwin.core.dynamics import DynamicScene, LinearTrajectory
moving = DynamicScene(
scene, # the wall Scene from the quickstart, structure_id=1
structure_trajectories={
1: LinearTrajectory(origin=torch.zeros(3), velocity=torch.tensor([0.0, 0.0, 0.5])),
},
)
result = radar.simulate(moving, targets, times=(0.0, 0.1), reflections=1)Moving geometry changes which paths exist, so by default Radar rediscovers the topology at every observation. For deforming meshes, pass structure_deformations={structure_id: deformation} instead; Simulate SMPL humans shows one.
To put a scatterer on a moving structure without computing its position, use StructureTargets(rcs=..., structure_ids=(1,)). It reads the anchor Core publishes for each structure with rigid motion.
Move the antennas#
The radar pose (position, look_at, up) is fixed for a call. To move antenna phase centres during a frame, for example a radar on a moving platform, declare Core endpoints at the element positions, give them trajectories, and tell Radar which endpoint is which element with SensorEndpointIds:
from witwin.core import AntennaState, Scene
from witwin.core.dynamics import DynamicScene, LinearTrajectory
from witwin.radar.simulation import SensorEndpointIds
tx_ids = tuple(77110 + i for i in range(radar.num_tx))
rx_ids = tuple(77210 + i for i in range(radar.num_rx))
endpoints = [AntennaState(i, "tx", p.cpu()) for i, p in zip(tx_ids, radar.tx_pos)]
endpoints += [AntennaState(i, "rx", p.cpu()) for i, p in zip(rx_ids, radar.rx_pos)]
world = DynamicScene(
Scene(structures=(), endpoints=endpoints),
endpoint_trajectories={i: LinearTrajectory(origin=(0, 0, 0), velocity=(0.3, 0, 0)) for i in tx_ids + rx_ids},
)
result = radar.simulate(world, targets, times=(0.0,), reflections=0,
endpoints=SensorEndpointIds(transmitters=tx_ids, receivers=rx_ids))transmitters and receivers list Core antenna IDs in the radar's TX and RX array order. The IDs must be distinct and non-empty. A DynamicScene with endpoint trajectories but no endpoints= raises ValueError that names sensor_endpoints. Endpoint trajectories move phase centres only; the antenna orientation stays the radar pose.
Choose the motion sampling#
motion=None means Motion.auto(), which picks per-ADC sampling for any of the motions above. Pick a different kind explicitly when you need speed:
from witwin.radar import Motion
reference = radar.simulate(scene, targets, times=times, motion=Motion.adc())
stop_and_hop = radar.simulate(scene, targets, times=times, motion=Motion.chirp())
adaptive = radar.simulate(scene, targets, times=times, motion=Motion.adaptive(phase_error=0.01))
print(adaptive.path_set_complete, adaptive.motion_sampling_exhaustive)Motion.chirp()freezes the geometry inside each chirp slot and is usually much faster thanadc.Motion.adaptive(...)interpolates between error-checked probes. FMCW only.Motion.static()raises for any of the motions above.rediscover_every_frames=nonauto,static,chirporadcskips topology discovery between everynframes and reportspath_set_complete=False.
Frame times#
times is the sequence of frame start times in seconds. It must be finite and strictly increasing. Each frame is simulated at its own start time, so times=(0.0, 0.1, 0.2) gives three frames 100 ms apart, regardless of the waveform's frame length.