WiTwin Radar
WiTwin Radar is a GPU-accelerated, differentiable radar simulator. A simulation consumes a witwin.core world and one-way propagation from witwin.channel, composes round trips, applies scattering and sensor/frontend effects, synthesizes radar waveforms, and produces typed signal-processing products.
The repository uses a breaking, concept-axis architecture. Compatibility modules and deprecated aliases are intentionally not retained.
For dynamic FMCW, Radar.simulate(..., motion=Motion.adaptive(...))
uses phase-controlled temporal interpolation and batched propagation. Import Motion from
witwin.radar. Per-ADC sampling remains the exhaustive reference and is what the default
Motion.auto() resolves to whenever anything moves; adaptive topology
probes cannot certify arbitrarily brief events between observations. Receiver effects operate on beat samples
before the requested range transform, and shared oscillator noise uses actual timestamps and path delays.
Adaptive replay reuses a complete LOS topology in an empty world and batches propagation, joins,
antenna weighting and ADC synthesis. The measured 128-sample × 1024-chirp rotor scene takes
270.99 ms per frame on RTX 5080 at the default interpolation order, or 174.28 ms at
nodes=5 (the 2026-09-17 comparison);
see performance evidence and limits.
Dynamic FMCW simulation refreshes the scene at each ADC observation by default, including TDM timing. Motion.chirp() selects the faster stop-and-hop approximation. See docs/pipeline_guide.md for trajectories, path completeness, and typed micro-Doppler timestamps.
Installation and runtime
pip install witwin-radar[channel]
CUDA is required for propagation and native waveform synthesis. CPU construction and PyTorch signal-processing workflows remain useful without a GPU. Linux and Windows are supported.
Release policy is explicit and conservative:
- CPython 3.10-3.14;
- Linux wheels target
manylinux_2_28_x86_64; - one packaged
_radar_nativelibrary plus its identity sidecars; - exact Torch/CUDA/ABI runtime identity;
- no JIT fallback and no success-by-loader-refusal path.
The release build matrix currently uses Torch 2.10 with CUDA 12.8 for each supported Python version. A different Torch or CUDA identity requires a separately built and validated artifact.
Architecture
witwin.core Scene/DynamicScene
|
v
Radar simulation session -> Channel propagation -> round-trip path composition
| |
+-> scattering -> sensor/frontend -----+
v
FMCW / OFDM / pulsed synthesis
v
typed processing products
Production ownership is intentionally shallow:
witwin/radar/radar.py— the flatRadarrecord, its waveforms, its pose, and the flat-configuration loader;witwin/radar/targets.py— the target vocabulary: positions, cross section or strength, trajectory, and identity;witwin/radar/simulation.py— scene-session execution, motion sampling, and frame results;witwin/radar/channel.py— the only production Channel importer;witwin/radar/propagation.pyandwitwin/radar/paths.py— propagation policy and round-trip composition;witwin/radar/scattering.py,sensors.py, andfrontend.py— radar physics around the path;witwin/radar/synthesis/— native waveform synthesis;witwin/radar/processing/— range, Doppler, angle, detection, and tracking products;witwin/radar/cuda/— the native runtime and kernels.
FMCW: spectrum first
FMCW synthesis directly generates the Dirichlet range spectrum in native CUDA by default. The default is output_domain="spectrum" in both the typed FMCW spec and the flat radar configuration. Set output_domain="beat" only when a caller explicitly needs a synthesized time-domain beat signal.
The result carries named axes and an output-domain field. Downstream processing uses that metadata, so it does not apply an extra range FFT to a spectrum or omit the FFT for beat samples.
Main API
Radar is one flat immutable record in SI units. A sub-record appears only where a field has variants (the waveform, the antenna pattern, the motion sampling) or where several numbers are coupled (Noise, Agc, Adc); the array, the pose and the one-number receive stages are fields.
from witwin.radar import Motion, Noise, PointTargets, Radar
radar = Radar.from_dict(CONFIG, noise=Noise(figure=10.0), seed=20260727, look_at=(0.0, 0.0, -3.0))
targets = PointTargets(positions=[(0.0, 0.0, -3.0)], rcs=1.0)
result = radar.simulate(scene, targets, times=(0.0, 0.1, 0.2), los=True, reflections=1, motion=Motion.auto())
Radar.from_dict reads the flat FMCW configuration format examples and config files use, in its vendor units, and is the only place those units are read. Every other field is a keyword override in SI, so a receive chain and a pose attach in the same call. Radar.from_json loads the same mapping from a file. Keys outside the format are refused by name rather than stored.
The radar has four verbs, and they are two halves and their fusion:
radar.trace(scene, targets, times=...)runs the world half — world sampling, Channel epochs, round-trip composition, scattering and antenna weighting — and returns aPaths;radar.echo(paths)runs the instrument half — waveform synthesis, the receive chain, the output domain and the processing axes — and returns aResult;radar.simulate(...)fuses the two and stacks every frame;radar.stream(...)fuses them and yields one frame at a time, so a sequence too long to hold as a stacked cube is still producible.
All four share one session loop and one synthesis route, so radar.echo(radar.trace(...)) is bit-identical to radar.simulate(...), asserted with torch.equal across every motion kind, both FMCW output domains, with and without a receive chain, and with oscillator phase noise.
The split buys reuse and costs retention. One trace can be echoed by several receive chains, seeds or output domains without re-solving the world, and a radar the rows do not describe is refused by name — including a phase-noise receiver against paths traced without ADC instants. Against that, a Paths holds every evaluated observation's rows, roughly twenty bytes per live row summed over every evaluated observation of every frame, so an ADC-refreshed sequence is expensive. simulate never pays that: a frame's observations stay a generator it drains one at a time. An adaptive trace retains its accepted partition rather than its schedule, so its retained row count is the probes and not the observations.
Radar.simulate(...) accepts a Core scene, a required PointTargets or StructureTargets record that says where the scatterers are and how strongly they scatter, the frame times, the propagation request as los and reflections, the in-frame resampling as motion, and the differentiation mode as grad. Radar.trace(...) takes the same arguments. simulate and echo both return Result, whose cube is organized as [frame, TX, RX, slow, fast], whose axis_names names those axes, and whose axes is the ProcessingAxes record every processing stage reads, built once from the waveform spec and the array that produced the cube.
Radar.stream(...) runs the same session and yields each frame as its own one-frame Result. Use it when the sequence is longer than the stacked cube can be held in device memory; the frames are bit-exact against simulate and peak allocation no longer scales with the frame count.
A radar is never edited in place: Radar.replace(...) and Radar.to(device) return a new one, and a radar retains nothing from a run, so a result is the only thing that can say which frame a diagnostic describes.
The root exports the whole happy path in sixteen names: Adc, Agc, Aspect, Fmcw, Frame, Motion, Noise, Ofdm, Paths, Pattern, PointTargets, Pulsed, Radar, Result, StructureTargets and processing. Advanced records stay importable from their owner modules.
Signal processing is exported through witwin.radar.processing; typed products include processing cubes, range profiles, Range-Doppler maps, beam cubes, detections, and point clouds. result.frame(i) returns the Frame that pairs one frame's cube with that metadata; its processing_cube(), range_profile(), range_doppler(), array() and points() are facades over that package and compute nothing themselves.
See docs/pipeline_guide.md for the full contract and examples/single_point.py for a maintained end-to-end example.
Tests and governance
pytest tests/
pytest tests/ --gpu
python ci/run_ci_tier.py quick
The quick tier includes canonical Ruff formatting/lint, exact-clone detection, architectural, public-surface, documentation, release-claim, workflow-reference, and compatibility-removal gates. Required-Channel workflows install the Channel extra, record its build fingerprint, and permit zero skips caused by a missing Channel runtime.
No benchmark, GPU result, wheel load, or remote workflow is claimed as executed merely because its command exists. Current performance evidence and outstanding measurements are documented in PERFORMANCE.md.
Examples
python -m examples.single_point
python -m examples.music_imaging
python -m examples.rgbd_range_doppler --input path/to/depths.npz
All maintained scene-driven examples require CUDA and Channel.
Documentation
- Pipeline:
docs/pipeline_guide.md - Development standard:
docs/dev/standards/radar-adr-021-code-layout-comments-and-mathematical-ownership.md - Consolidation plan:
docs/dev/plans/radar-concept-axis-layout-and-module-consolidation-plan.md - Governance inventory:
docs/dev/audit/radar-governance-debt-and-drift-inventory.md - AD capability matrix:
docs/dev/radar-ad-capability-matrix.md - AD tape/budget ledger:
docs/dev/ad-tape-and-budget-ledger.md
License and citation
WiTwin Radar uses the WiTwin dual-license model. See the WiTwin licensing page. The simulator is derived from RF-Genesis; cite the RF-Genesis SenSys 2023 paper when that prior work is relevant.
Metadata
Release files for witwin-radar 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| witwin_radar-0.4.1.tar.gz | 1.3 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| witwin_radar-0.4.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| witwin_radar-0.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 | Details |
Total release size: 5.9 MB
Release files / witwin_radar-0.4.1.tar.gz
| Download URL | witwin_radar-0.4.1.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f5c4ceb6756526137b1cb40a317a7bc30706ef4a8b7ab7d2806fde41696cf8b
|
|
BLAKE2b-256 checksum How to use checksums |
baaf1d7bd98a6ef847c2b951dfe400ffeef010cf5571b05b04e0ede51d997776
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logRelease files / witwin_radar-0.4.1-py3-none-win_amd64.whl
| Download URL | witwin_radar-0.4.1-py3-none-win_amd64.whl |
|---|---|
| Size | 2.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
69550d0627e7ef431c938cffa1d00805eeb5eb410491bd6f438ff463baa5e69b
|
|
BLAKE2b-256 checksum How to use checksums |
1736e45c77ae83518d3e8eeb707edbfa62db7c310a1136b8c3caaf6df4f0876d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logRelease files / witwin_radar-0.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | witwin_radar-0.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
693466aef87f78b8f25664b9de2be73e2deb4be01648ad1e3a1836f13290bede
|
|
BLAKE2b-256 checksum How to use checksums |
a05c072bb0ec95f2f9a0e4b411a852adb649e84ebc0315816fe003cf85a09db6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency log