Point target in front of a wall
Simulate a 77 GHz MIMO radar, a point target and a concrete wall, check the echo against the radar equation, and find the multipath peak.
In this tutorial you simulate the smallest complete scene: a 3-TX, 4-RX radar at the origin, a 1 m² point target 3 m in front of it, and a concrete wall at 5 m. You then check two things whose answer you know in advance: the echo strength against the free-space radar equation, and the position of the wall's multipath peak. It is a trimmed version of examples/single_point.py in the Radar repository.
You need a working installation and a CUDA GPU.
The script#
import math
import torch
from witwin.core import Mesh, PhysicalMaterial, Scene, Structure
from witwin.radar import Noise, PointTargets, Radar
from witwin.radar.policy import SPEED_OF_LIGHT_M_PER_S
CONFIG = {
"num_tx": 3, "num_rx": 4, "fc": 77e9, "slope": 60.012, "adc_samples": 256,
"adc_start_time": 6, "sample_rate": 4400, "idle_time": 7, "ramp_end_time": 65,
"chirp_per_frame": 128, "power": 15,
"tx_loc": [[0, 0, 0], [4, 0, 0], [2, 1, 0]],
"rx_loc": [[-6, 0, 0], [-5, 0, 0], [-4, 0, 0], [-3, 0, 0]],
}
TARGET = (0.0, 0.0, -3.0)
RCS = 1.0
WALL_Z = -5.0
radar = Radar.from_dict(CONFIG, noise=Noise(figure=10.0), seed=20260727, look_at=(0.0, 0.0, -1.0))
wall = Mesh(
vertices=torch.tensor([(-2.0, -2.0, WALL_Z), (2.0, -2.0, WALL_Z), (2.0, 2.0, WALL_Z), (-2.0, 2.0, WALL_Z)]),
faces=torch.tensor([(0, 1, 2), (0, 2, 3)]),
recenter=False, fill_mode="surface", topology_diagnostics=False,
)
scene = Scene(
structures=(Structure(geometry=wall, material=PhysicalMaterial(name="concrete", eps_r=5.24, sigma_e=0.0462),
structure_id=1, material_id=1, assignment_id=1, surface_id=1),),
endpoints=[],
)
targets = PointTargets(positions=[TARGET], rcs=RCS)
result = radar.simulate(scene, targets, times=(0.0, 0.1, 0.2), los=True, reflections=1)
print("cube", tuple(result.cube.shape))
print("compiles", result.compile_count, "discoveries", result.discovery_count, "epochs", result.epochs)
paths = result.last_radar_paths
print("rows", paths.path_count, "pairs", paths.sensor_pair_count)
# Free-space radar equation for the direct round trip.
wavelength = SPEED_OF_LIGHT_M_PER_S / radar.carrier
r = math.dist((0.0, 0.0, 0.0), TARGET)
spreading = wavelength / (4 * math.pi * r)
strength = math.sqrt(4 * math.pi * RCS) / wavelength
predicted = math.sqrt(radar.transmit_power_watts) * spreading * strength * spreading
measured = float(paths.complex_transfer_ref.abs().max())
print(f"|C| measured {measured:.6e}, radar equation {predicted:.6e}")
frame = result.frame(0)
rd = frame.range_doppler(window="hann")
combined = rd.data.reshape(-1, *rd.data.shape[-2:]).sum(dim=0)
response = combined.abs().amax(dim=0)
peak = int(response.argmax())
print(f"range peak {float(frame.axes.range_m[peak]):.4f} m (bin {frame.axes.range_bin_m:.4f} m)")
image_range = 0.5 * (r + math.dist((0.0, 0.0, 2 * WALL_Z), TARGET))
k = int(round(image_range / frame.axes.range_bin_m))
ratio = float(response[k - 2:k + 3].max() / response[peak])
print(f"multipath near {image_range:.3f} m, {20 * math.log10(ratio):.1f} dB below the direct peak")Output on an RTX 5080:
cube (3, 3, 4, 128, 256)
compiles 1 discoveries 1 epochs (0, 0, 0)
rows 48 pairs 12
|C| measured 1.726919e-06, radar equation 1.726922e-06
range peak 3.0051 m (bin 0.0429 m)
multipath near 5.000 m, -10.1 dB below the direct peakWhat the output shows#
One compile, one discovery
Nothing moves, so Radar compiles the Scene once and discovers the path set once for all three frames. Every frame belongs to epoch 0. See Simulation pipeline.
48 rows over 12 sensor pairs
Each of the 12 TX-RX pairs has four round trips: out and back on the direct path, out direct and back via the wall, the reverse, and via the wall both ways.
last_radar_pathsholds the composed rows of the last frame.The radar equation
The strongest row is the direct round trip. Its magnitude is the product of the transmit amplitude
sqrt(P_tx), two free-space spreading factorslambda / (4 pi R)and the target strengthsqrt(4 pi sigma) / lambda. The simulated value agrees to 2e-6 relative.complex_transfer_refis the transfer coefficient at the carrier, before the waveform and the receiver chain.The range peak
The coherent sum over the 12 pairs peaks at 3.005 m, within one 4.3 cm range bin of the true 3 m.
The multipath peak
The wall mirrors the target to
z = -7 m. A round trip that goes direct one way and via the wall the other way has the mean of the two lengths, 5 m. Both cross terms land there, about 10 dB below the direct peak. The wall-both-ways path is at 7 m.
Things to try#
- Set
reflections=0and the 5 m peak disappears, leaving 12 rows. - Change
eps_rof the wall. A larger permittivity raises the multipath peak. - Remove
noise=...fromRadar.from_dict. The cube is then noiseless, and CFAR inframe.points()detects sidelobes rather than only targets. See Detect targets and build point clouds.