witwin-radar 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. witwin_radar-0.0.1/.github/workflows/publish-witwin-radar.yml +92 -0
  2. witwin_radar-0.0.1/.gitignore +229 -0
  3. witwin_radar-0.0.1/FEATURE_LIST.md +55 -0
  4. witwin_radar-0.0.1/PERFORMANCE.md +146 -0
  5. witwin_radar-0.0.1/PKG-INFO +145 -0
  6. witwin_radar-0.0.1/README.md +133 -0
  7. witwin_radar-0.0.1/batch_generate.py +352 -0
  8. witwin_radar-0.0.1/examples/__init__.py +0 -0
  9. witwin_radar-0.0.1/examples/amass_pointcloud.ipynb +311 -0
  10. witwin_radar-0.0.1/examples/amass_pointcloud.py +117 -0
  11. witwin_radar-0.0.1/examples/humanbody.ipynb +428 -0
  12. witwin_radar-0.0.1/examples/humanbody.py +72 -0
  13. witwin_radar-0.0.1/examples/mesh_scene.ipynb +151 -0
  14. witwin_radar-0.0.1/examples/mesh_scene.py +92 -0
  15. witwin_radar-0.0.1/examples/music_imaging.ipynb +235 -0
  16. witwin_radar-0.0.1/examples/music_imaging.py +67 -0
  17. witwin_radar-0.0.1/examples/music_imaging_body.ipynb +391 -0
  18. witwin_radar-0.0.1/examples/single_point.ipynb +139 -0
  19. witwin_radar-0.0.1/examples/single_point.py +57 -0
  20. witwin_radar-0.0.1/examples/timeline.ipynb +237 -0
  21. witwin_radar-0.0.1/experiments/gen_amass_video.py +270 -0
  22. witwin_radar-0.0.1/pyproject.toml +27 -0
  23. witwin_radar-0.0.1/requirements.txt +9 -0
  24. witwin_radar-0.0.1/tests/benchmark.py +156 -0
  25. witwin_radar-0.0.1/tests/conftest.py +221 -0
  26. witwin_radar-0.0.1/tests/core/__init__.py +0 -0
  27. witwin_radar-0.0.1/tests/core/test_antenna_pattern.py +174 -0
  28. witwin_radar-0.0.1/tests/core/test_differentiable_to_mesh.py +55 -0
  29. witwin_radar-0.0.1/tests/core/test_multipath_smoke.py +113 -0
  30. witwin_radar-0.0.1/tests/core/test_noise_model.py +270 -0
  31. witwin_radar-0.0.1/tests/core/test_path_runtime.py +386 -0
  32. witwin_radar-0.0.1/tests/core/test_polarization.py +122 -0
  33. witwin_radar-0.0.1/tests/core/test_radar_config.py +434 -0
  34. witwin_radar-0.0.1/tests/core/test_radar_pose.py +147 -0
  35. witwin_radar-0.0.1/tests/core/test_receiver_chain.py +148 -0
  36. witwin_radar-0.0.1/tests/core/test_scene_api.py +33 -0
  37. witwin_radar-0.0.1/tests/core/test_scene_motion.py +342 -0
  38. witwin_radar-0.0.1/tests/core/test_scene_to_mitsuba.py +242 -0
  39. witwin_radar-0.0.1/tests/core/test_simulation_smoke.py +280 -0
  40. witwin_radar-0.0.1/tests/core/test_waveform.py +98 -0
  41. witwin_radar-0.0.1/tests/debug_offset.py +341 -0
  42. witwin_radar-0.0.1/tests/sigproc/__init__.py +0 -0
  43. witwin_radar-0.0.1/tests/sigproc/test_aoa.py +213 -0
  44. witwin_radar-0.0.1/tests/sigproc/test_cfar.py +218 -0
  45. witwin_radar-0.0.1/tests/sigproc/test_pointcloud_pipeline.py +153 -0
  46. witwin_radar-0.0.1/tests/sigproc/test_range_doppler.py +128 -0
  47. witwin_radar-0.0.1/tests/solvers/__init__.py +0 -0
  48. witwin_radar-0.0.1/tests/solvers/test_chirp_cross.py +164 -0
  49. witwin_radar-0.0.1/tests/solvers/test_mimo_cross.py +183 -0
  50. witwin_radar-0.0.1/tests/solvers/test_solver_edge.py +127 -0
  51. witwin_radar-0.0.1/tests/test_gradient_flow.py +447 -0
  52. witwin_radar-0.0.1/tests/validation/__init__.py +0 -0
  53. witwin_radar-0.0.1/tests/validation/test_multi_target.py +229 -0
  54. witwin_radar-0.0.1/tests/validation/test_single_target.py +313 -0
  55. witwin_radar-0.0.1/tests/verify.py +173 -0
  56. witwin_radar-0.0.1/tests/verify_mimo.py +88 -0
  57. witwin_radar-0.0.1/tests/verify_triangle.py +175 -0
  58. witwin_radar-0.0.1/witwin/radar/__init__.py +81 -0
  59. witwin_radar-0.0.1/witwin/radar/antenna_pattern.py +313 -0
  60. witwin_radar-0.0.1/witwin/radar/config.py +211 -0
  61. witwin_radar-0.0.1/witwin/radar/material.py +42 -0
  62. witwin_radar-0.0.1/witwin/radar/motion.py +181 -0
  63. witwin_radar-0.0.1/witwin/radar/noise.py +258 -0
  64. witwin_radar-0.0.1/witwin/radar/polarization.py +156 -0
  65. witwin_radar-0.0.1/witwin/radar/radar.py +223 -0
  66. witwin_radar-0.0.1/witwin/radar/receiver_chain.py +173 -0
  67. witwin_radar-0.0.1/witwin/radar/renderer.py +611 -0
  68. witwin_radar-0.0.1/witwin/radar/result.py +107 -0
  69. witwin_radar-0.0.1/witwin/radar/scene.py +728 -0
  70. witwin_radar-0.0.1/witwin/radar/sensor.py +95 -0
  71. witwin_radar-0.0.1/witwin/radar/sigproc/__init__.py +39 -0
  72. witwin_radar-0.0.1/witwin/radar/sigproc/cfar.py +229 -0
  73. witwin_radar-0.0.1/witwin/radar/sigproc/music.py +146 -0
  74. witwin_radar-0.0.1/witwin/radar/sigproc/pointcloud.py +608 -0
  75. witwin_radar-0.0.1/witwin/radar/simulation.py +275 -0
  76. witwin_radar-0.0.1/witwin/radar/solvers/__init__.py +80 -0
  77. witwin_radar-0.0.1/witwin/radar/solvers/_runtime.py +223 -0
  78. witwin_radar-0.0.1/witwin/radar/solvers/dirichlet.slang +439 -0
  79. witwin_radar-0.0.1/witwin/radar/solvers/radar.slang +237 -0
  80. witwin_radar-0.0.1/witwin/radar/solvers/solver_dirichlet.py +302 -0
  81. witwin_radar-0.0.1/witwin/radar/solvers/solver_pytorch.py +68 -0
  82. witwin_radar-0.0.1/witwin/radar/solvers/solver_slang.py +204 -0
  83. witwin_radar-0.0.1/witwin/radar/timeline.py +301 -0
  84. witwin_radar-0.0.1/witwin/radar/types.py +45 -0
@@ -0,0 +1,92 @@
1
+ name: Publish witwin-radar
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ if: startsWith(github.event.release.tag_name, 'witwin-radar-v') && !github.event.release.prerelease
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+ environment:
15
+ name: pypi
16
+ url: https://pypi.org/project/witwin-radar/
17
+
18
+ steps:
19
+ - name: Check out repository
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.11"
26
+
27
+ - name: Resolve package directory
28
+ id: package_dir
29
+ shell: bash
30
+ run: |
31
+ if [ -f pyproject.toml ]; then
32
+ echo "path=." >> "$GITHUB_OUTPUT"
33
+ elif [ -f radar/pyproject.toml ]; then
34
+ echo "path=radar" >> "$GITHUB_OUTPUT"
35
+ else
36
+ echo "Unable to find pyproject.toml for the witwin-radar package." >&2
37
+ exit 1
38
+ fi
39
+
40
+ - name: Validate release tag and package version
41
+ shell: bash
42
+ env:
43
+ PACKAGE_DIR: ${{ steps.package_dir.outputs.path }}
44
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
45
+ run: |
46
+ python - <<'PY'
47
+ import os
48
+ import re
49
+ import sys
50
+ import tomllib
51
+ from pathlib import Path
52
+
53
+ tag = os.environ["RELEASE_TAG"]
54
+ match = re.fullmatch(r"witwin-radar-v(?P<version>[0-9A-Za-z.+_-]+)", tag)
55
+ if match is None:
56
+ print(f"Release tag '{tag}' must match 'witwin-radar-v<version>'.", file=sys.stderr)
57
+ raise SystemExit(1)
58
+
59
+ expected_version = match.group("version")
60
+ pyproject_path = Path(os.environ["PACKAGE_DIR"]) / "pyproject.toml"
61
+ data = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
62
+
63
+ project = data["project"]
64
+ package_name = project["name"]
65
+ package_version = project["version"]
66
+
67
+ if package_name != "witwin-radar":
68
+ print(f"Expected package name 'witwin-radar', found '{package_name}'.", file=sys.stderr)
69
+ raise SystemExit(1)
70
+
71
+ if package_version != expected_version:
72
+ print(
73
+ f"Release tag version '{expected_version}' does not match pyproject version '{package_version}'.",
74
+ file=sys.stderr,
75
+ )
76
+ raise SystemExit(1)
77
+
78
+ print(f"Validated {package_name} {package_version} from {pyproject_path}.")
79
+ PY
80
+
81
+ - name: Build distributions
82
+ shell: bash
83
+ env:
84
+ PACKAGE_DIR: ${{ steps.package_dir.outputs.path }}
85
+ run: |
86
+ python -m pip install --upgrade pip build
87
+ python -m build "$PACKAGE_DIR"
88
+
89
+ - name: Publish distributions to PyPI
90
+ uses: pypa/gh-action-pypi-publish@release/v1
91
+ with:
92
+ packages-dir: ${{ steps.package_dir.outputs.path }}/dist
@@ -0,0 +1,229 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ *.lock
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py.cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+
76
+ # PyBuilder
77
+ .pybuilder/
78
+ target/
79
+
80
+ # Jupyter Notebook
81
+ .ipynb_checkpoints
82
+
83
+ # IPython
84
+ profile_default/
85
+ ipython_config.py
86
+
87
+ # pyenv
88
+ # For a library or package, you might want to ignore these files since the code is
89
+ # intended to run in multiple environments; otherwise, check them in:
90
+ # .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ # Pipfile.lock
98
+
99
+ # UV
100
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
101
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
102
+ # commonly ignored for libraries.
103
+ # uv.lock
104
+
105
+ # poetry
106
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
108
+ # commonly ignored for libraries.
109
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110
+ # poetry.lock
111
+ # poetry.toml
112
+
113
+ # pdm
114
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
115
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
116
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
117
+ # pdm.lock
118
+ # pdm.toml
119
+ .pdm-python
120
+ .pdm-build/
121
+
122
+ # pixi
123
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
124
+ # pixi.lock
125
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
126
+ # in the .venv directory. It is recommended not to include this directory in version control.
127
+ .pixi
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ .slangtorch_cache/
187
+
188
+ # RadarTwin project data
189
+ data/
190
+ output/
191
+ models/
192
+ *.tar.bz2
193
+ *.tar.xz
194
+ *.tar.gz
195
+ activate.sh
196
+
197
+ # PyCharm
198
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
199
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
200
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
201
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
202
+ # .idea/
203
+
204
+ # Abstra
205
+ # Abstra is an AI-powered process automation framework.
206
+ # Ignore directories containing user credentials, local state, and settings.
207
+ # Learn more at https://abstra.io/docs
208
+ .abstra/
209
+
210
+ # Visual Studio Code
211
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
212
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
213
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
214
+ # you could uncomment the following to ignore the entire vscode folder
215
+ # .vscode/
216
+
217
+ # Ruff stuff:
218
+ .ruff_cache/
219
+
220
+ # PyPI configuration file
221
+ .pypirc
222
+
223
+ # Marimo
224
+ marimo/_static/
225
+ marimo/_lsp/
226
+ __marimo__/
227
+
228
+ # Streamlit
229
+ .streamlit/secrets.toml
@@ -0,0 +1,55 @@
1
+ # Radar Feature List
2
+
3
+ ## Public API
4
+
5
+ - Declarative simulation flow: `Scene -> Simulation -> Result`
6
+ - Radar pose can be controlled explicitly with `Sensor(...)` on `Radar`, `Renderer`, or `Simulation`
7
+ - Scene assembly uses `Scene.set_sensor(...)`, `Scene.add_structure(...)`, `Scene.add_mesh(...)`, `Scene.add_smpl(...)`, and `Scene.add_structure_motion(...)`
8
+ - Multi-radar orchestration is available via `Simulation.mimo_group(...)`, `RadarSpec`, and `MultiResult`
9
+ - Optional per-structure motion is available through `Scene.add_structure_motion(...)`, `Scene.update_structure_motion(...)`, and `Scene.clear_structure_motion(...)`
10
+ - Public string-literal API types: `SolverBackend`, `DetectorType`, `SamplingMode`, and `MotionSampling`
11
+ - Low-level radar solver entrypoint: `Radar.chirp()`, `Radar.frame()`, `Radar.mimo()`, and `Radar.apply_noise()`
12
+ - Ray-tracing entrypoint: `Renderer.trace()` returns `TraceResult(points, intensities)` and also carries `entry_points`, `fixed_path_lengths`, and `depths` for generalized path tracing
13
+ - `Result.signal()`, `Result.trace_points()`, `Result.trace_intensities()`, `Result.trace_entry_points()`, `Result.trace_fixed_path_lengths()`, and `Result.trace_depths()` provide semantic tensor access, with `Result.tensor(...)` retained as a generic fallback
14
+
15
+ ## Configuration
16
+
17
+ - `RadarConfig` frozen schema validates required radar fields and antenna layouts
18
+ - Optional `antenna_pattern` config defaults to a broadside dipole and also supports separable `x/y` 1D gain curves or a direct 2D gain map
19
+ - Optional `noise_model` config supports thermal noise, quantization noise, and phase noise with optional deterministic seeding
20
+ - Optional `polarization` config supports simplified TX/RX polarization vectors with alias strings (`horizontal` / `vertical`) or per-element 3D vectors
21
+ - Optional `receiver_chain` config supports `lna`, `agc`, and `adc` stages plus absolute TX-power scaling via `config["power"]`
22
+ - `Radar` accepts `RadarConfig`, `dict`, or JSON config path
23
+ - `Simulation` accepts the same validated config inputs as `Radar`
24
+ - `Renderer(...)` and `Simulation.mimo(...)` accept `multipath`, `max_reflections`, and `ray_batch_size`
25
+ - `Simulation.mimo(...)` and `Simulation.mimo_group(...)` accept `motion_sampling="frame" | "chirp"` for dynamic scenes
26
+
27
+ ## Backend Execution
28
+
29
+ - Three solver backends: `pytorch`, `slang`, `dirichlet`
30
+ - Backend-specific runtime state lives on `radar.solver`, including Dirichlet FFT metadata such as `pad_factor` and `N_fft`
31
+ - `Radar(device=...)` validates CUDA availability explicitly; `slang` and `dirichlet` require CUDA, while `pytorch` honors the selected device
32
+ - Time-domain outputs from `Radar.chirp()`, `Radar.frame()`, and `Radar.mimo()` automatically apply `noise_model` when configured; `radar.mimo(..., freq_domain=True)` rejects built-in noise injection
33
+ - Time-domain outputs from `Radar.chirp()`, `Radar.frame()`, and `Radar.mimo()` automatically apply `receiver_chain` when configured; enabling it also moves `Radar.gain` onto an absolute transmit-voltage scale
34
+ - `receiver_chain.adc` and `noise_model.quantization` are mutually exclusive so only one ADC quantizer is active
35
+
36
+ ## Rendering And Dynamics
37
+
38
+ - `Renderer.trace()` has a single public signature with no ignored `spp` parameter
39
+ - `Scene.compile_renderables(time=...)` and `Renderer.trace(time=...)` expose time-dependent geometry for dynamic scenes
40
+ - Multipath tracing is available for `sampling="pixel"` and uses radar-center path tracing with configurable maximum specular reflection depth
41
+ - Solver backends consume generalized path samples and apply FSPL from the total `tx -> bounces -> scatter -> rx` distance
42
+ - When `polarization` is configured, traced path normals are propagated through the runtime and used for simplified reflection/projection coupling
43
+ - Shared core geometry constructors default to `device=None`, while radar `Scene(...)` owns device placement and defaults to CUDA
44
+ - `Timeline.from_motion()` uses the renderer trace contract directly
45
+ - Dynamic structure motion supports rigid `translation`, `rotation`, and `parent` inheritance so rotational Doppler can be modeled directly from the scene
46
+ - `radar.mimo(..., freq_domain=True)` remains available for Dirichlet frequency-domain output
47
+
48
+ ## Signal Processing
49
+
50
+ - `process_pc(..., detector=...)` accepts the validated detector set `{"cfar", "topk"}`
51
+ - `frame2pointcloud(...)` requires a `radar` argument so TDM-MIMO compensation is never skipped silently
52
+ - `PointCloudProcessConfig` provides the normalized point-cloud extraction config surface
53
+ - `range_fft(...)`, `doppler_fft(...)`, `clutter_removal(...)`, `process_pc(...)`, and `process_rd(...)` keep tensor inputs on the PyTorch path and use `torch.fft` for GPU-native DSP
54
+ - `ca_cfar_2d(...)`, `ca_cfar_2d_fast(...)`, and `os_cfar_2d(...)` return `(detections, threshold_map)` with a consistent CFAR contract
55
+ - `ca_cfar_2d(...)`, `ca_cfar_2d_fast(...)`, and `os_cfar_2d(...)` all accept NumPy arrays and PyTorch tensors; the reference CA/OS paths now stay on the torch device instead of falling back to CPU
@@ -0,0 +1,146 @@
1
+ # Radar Signal Processing Performance Analysis
2
+
3
+ ## Overview
4
+
5
+ This report compares different parallelization strategies for FMCW radar signal processing:
6
+ - **Slang+FFT**: Compute time-domain chirp signal, then apply FFT
7
+ - **Dirichlet**: Direct frequency-domain computation using Dirichlet kernel (skips FFT)
8
+
9
+ Each method has three variants:
10
+ - **Internal (_i)**: Per-output-bin parallel, each thread loops through ALL targets
11
+ - **Chunked (_c)**: Per-output-bin parallel, targets split into chunks, no atomics
12
+ - **Per-target (_t)**: Per-target parallel, each thread loops through ALL output bins, chunked atomics
13
+
14
+ ## Benchmark Results
15
+
16
+ Hardware: NVIDIA GPU with CUDA
17
+ Parameters: `adc_samples=400`, `pad_factor=16`, `N_fft=6400`, `num_bins=3200`
18
+
19
+ | Targets | PyTorch | Slang_i | Slang_c | Slang_t | Dir_i | Dir_c | Dir_t |
20
+ |---------|---------|---------|---------|---------|-------|-------|-------|
21
+ | 4 | 0.20 | 0.15 | 0.50 | 0.23 | 0.07 | 0.08 | 0.16 |
22
+ | 16 | 0.21 | 0.14 | 0.15 | 0.17 | 0.07 | 0.08 | 0.14 |
23
+ | 64 | 0.19 | 0.27 | 0.27 | 0.17 | 0.07 | 0.08 | 0.68 |
24
+ | 256 | 0.21 | 0.74 | 0.74 | 0.24 | 0.08 | 0.12 | 1.46 |
25
+ | 1024 | 0.27 | 2.99 | 0.75 | 0.34 | 0.16 | 0.13 | 1.65 |
26
+ | 4096 | 0.64 | 11.34 | 0.84 | 0.58 | 0.87 | 0.31 | 2.11 |
27
+ | 16384 | 3.69 | 45.86 | 1.91 | 1.25 | 1.77 | 1.33 | 3.79 |
28
+ | 65536 | 15.17 | 177.90 | 5.23 | 5.87 | 7.44 | 4.68 | 12.55 |
29
+ | 262144 | 54.28 | 677.15 | 21.04 | 19.09 | 28.44 | 14.50 | 42.31 |
30
+ | 1048576 | OOM | 2719.41 | 79.22 | 76.41 | 112.38 | **58.12** | 175.29 |
31
+
32
+ *Times in milliseconds (ms). OOM = Out of Memory.*
33
+
34
+ ## Method Comparison
35
+
36
+ ### 1. PyTorch (Batched Tensor Operations)
37
+ - **Pros**: Simple implementation, fast for small N
38
+ - **Cons**: O(N × T) memory, OOM for N > 262144
39
+ - **Memory**: ~6.7 GB for N=1M (N × T × 16 bytes for complex128)
40
+
41
+ ### 2. Slang Internal (Slang_i)
42
+ - **Strategy**: Each thread handles one time sample, loops through ALL targets
43
+ - **Parallelism**: T threads (400)
44
+ - **Pros**: Minimal memory O(T)
45
+ - **Cons**: Poor parallelism, very slow for large N
46
+ - **Bottleneck**: Serial loop over N targets per thread
47
+
48
+ ### 3. Slang Chunked (Slang_c)
49
+ - **Strategy**: 2D grid (time_blocks × target_chunks), each thread handles one time sample, loops through targets in its chunk
50
+ - **Parallelism**: T × num_chunks threads
51
+ - **Memory**: O(num_chunks × T) ≈ 26 MB for N=1M
52
+ - **Pros**: Good parallelism, no atomics needed
53
+ - **Performance**: 79.22ms @ 1M targets
54
+
55
+ ### 4. Slang Per-target (Slang_t)
56
+ - **Strategy**: Each thread handles one target, loops through ALL time samples
57
+ - **Parallelism**: N threads (chunked)
58
+ - **Memory**: O(num_chunks × T)
59
+ - **Atomics**: Required within each chunk (multiple targets write to same time index)
60
+ - **Performance**: 76.41ms @ 1M targets
61
+
62
+ ### 5. Dirichlet Internal (Dir_i)
63
+ - **Strategy**: Each thread handles one frequency bin, loops through ALL targets
64
+ - **Parallelism**: num_bins threads (3200)
65
+ - **Memory**: O(num_bins) ≈ 25 KB
66
+ - **Pros**: Minimal memory, skips FFT
67
+ - **Cons**: Poor parallelism for large N
68
+
69
+ ### 6. Dirichlet Chunked (Dir_c) - FASTEST
70
+ - **Strategy**: 2D grid (bin_blocks × target_chunks), each thread handles one bin, loops through targets in its chunk
71
+ - **Parallelism**: num_bins × num_chunks threads
72
+ - **Memory**: O(num_chunks × num_bins) ≈ 105 MB for N=1M
73
+ - **Pros**: Best parallelism, no atomics, skips FFT
74
+ - **Performance**: **58.12ms @ 1M targets**
75
+
76
+ ### 7. Dirichlet Per-target (Dir_t)
77
+ - **Strategy**: Each thread handles one target, loops through ALL frequency bins
78
+ - **Parallelism**: N threads (chunked)
79
+ - **Memory**: O(num_chunks × num_bins)
80
+ - **Atomics**: Required within each chunk (multiple targets write to same bin)
81
+ - **Performance**: 175.29ms @ 1M targets
82
+
83
+ ## Key Insights
84
+
85
+ ### Why Dirichlet is Faster than Slang+FFT
86
+ 1. **Skips FFT**: Dirichlet computes frequency-domain result directly
87
+ 2. **Same complexity**: Both are O(N × M) where M = num_bins or T
88
+ 3. **FFT overhead**: cuFFT adds ~20ms constant overhead
89
+
90
+ ### Why Chunked is Faster than Per-target
91
+ 1. **No atomics**: Chunked per-bin writes to separate memory locations
92
+ 2. **Cache locality**: Sequential bin iteration has better memory access patterns
93
+ 3. **Atomic contention**: Per-target has multiple threads writing to same bin indices
94
+
95
+ ### Why Per-target Chunked Improved Over Global Atomics
96
+ Previous per-target with global atomics: 132.58ms (Slang_t), 293.73ms (Dir_t)
97
+ Current per-target with chunked atomics: 76.41ms (Slang_t), 175.29ms (Dir_t)
98
+
99
+ **Improvement: ~1.7x faster** by reducing atomic contention scope from global to per-chunk.
100
+
101
+ ## Memory vs Performance Trade-off
102
+
103
+ | Method | Memory (N=1M) | Time (ms) | Notes |
104
+ |--------|---------------|-----------|-------|
105
+ | PyTorch | ~6.7 GB | OOM | N × T × 16 bytes |
106
+ | Slang_i | ~6 KB | 2719 | T × 8 bytes |
107
+ | Slang_c | ~26 MB | 79 | chunks × T × 8 bytes |
108
+ | Slang_t | ~26 MB | 76 | chunks × T × 8 bytes |
109
+ | Dir_i | ~25 KB | 112 | bins × 8 bytes |
110
+ | Dir_c | ~105 MB | **58** | chunks × bins × 8 bytes |
111
+ | Dir_t | ~105 MB | 175 | chunks × bins × 8 bytes |
112
+
113
+ ## Recommendations
114
+
115
+ 1. **For large N (>10K targets)**: Use **Dir_c** (Dirichlet Chunked)
116
+ - Best performance: 58ms @ 1M targets
117
+ - Reasonable memory: ~105 MB
118
+ - No atomic contention
119
+
120
+ 2. **For small N (<1K targets)**: Use **Dir_i** (Dirichlet Internal)
121
+ - Minimal memory overhead
122
+ - Fast enough for small N
123
+
124
+ 3. **When FFT is required**: Use **Slang_c** or **Slang_t**
125
+ - Similar performance (~76-79ms @ 1M)
126
+ - Slang_t slightly faster for very large N
127
+
128
+ 4. **Avoid**: Slang_i for large N (poor parallelism)
129
+
130
+ ## Parallelization Strategy Summary
131
+
132
+ ```
133
+ Per-bin (loop targets): Per-target (loop bins):
134
+ ┌─────────────────────┐ ┌─────────────────────┐
135
+ │ Thread 0: bin 0 │ │ Thread 0: target 0 │
136
+ │ for t in targets: │ │ for b in bins: │
137
+ │ accumulate │ │ atomic_add[b] │
138
+ ├─────────────────────┤ ├─────────────────────┤
139
+ │ Thread 1: bin 1 │ │ Thread 1: target 1 │
140
+ │ for t in targets: │ │ for b in bins: │
141
+ │ accumulate │ │ atomic_add[b] │
142
+ └─────────────────────┘ └─────────────────────┘
143
+ No atomics! Atomics required!
144
+ ```
145
+
146
+ The per-bin approach naturally avoids atomics because each thread writes to a unique output location.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: witwin-radar
3
+ Version: 0.0.1
4
+ Summary: Witwin Radar - Radar signal simulation
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: numpy>=1.24
8
+ Requires-Dist: scipy>=1.10
9
+ Requires-Dist: torch>=2.0
10
+ Requires-Dist: witwin>=0.0.1
11
+ Description-Content-Type: text/markdown
12
+
13
+ # WiTwin Radar
14
+
15
+ A GPU-accelerated, differentiable FMCW radar simulator for generating synthetic radar data from 3D scenes. It combines Mitsuba ray tracing with custom CUDA kernels and exposes a single public workflow:
16
+
17
+ `Scene -> Simulation -> Result`
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ import numpy as np
23
+ import torch
24
+
25
+ from witwin.radar import Radar
26
+ from witwin.radar.sigproc import process_pc, process_rd
27
+
28
+ config = {
29
+ "num_tx": 3,
30
+ "num_rx": 4,
31
+ "fc": 77e9,
32
+ "slope": 60.012,
33
+ "adc_samples": 256,
34
+ "adc_start_time": 6,
35
+ "sample_rate": 4400,
36
+ "idle_time": 7,
37
+ "ramp_end_time": 65,
38
+ "chirp_per_frame": 128,
39
+ "frame_per_second": 10,
40
+ "num_doppler_bins": 128,
41
+ "num_range_bins": 256,
42
+ "num_angle_bins": 64,
43
+ "power": 15,
44
+ "tx_loc": [[0, 0, 0], [4, 0, 0], [2, 1, 0]],
45
+ "rx_loc": [[-6, 0, 0], [-5, 0, 0], [-4, 0, 0], [-3, 0, 0]],
46
+ }
47
+
48
+ radar = Radar(config, backend="pytorch", device="cpu")
49
+
50
+ point = np.array([[0.0, 0.0, -3.0]], dtype=np.float32)
51
+ velocity = np.array([[0.0, 0.0, 0.01]], dtype=np.float32)
52
+
53
+
54
+ def interp(t):
55
+ positions = torch.tensor(point + velocity * t, dtype=torch.float32, device=radar.device)
56
+ intensities = torch.ones((positions.shape[0],), dtype=torch.float32, device=radar.device)
57
+ return intensities, positions
58
+
59
+
60
+ frame = radar.mimo(interp, t0=0)
61
+ pc = process_pc(radar, frame)
62
+ rd, _, ranges, vels = process_rd(radar, frame)
63
+ ```
64
+
65
+ ## Scene API
66
+
67
+ Use `Sensor(...)` to define the radar pose, `Scene.set_sensor(...)` to configure the default scene sensor, and `Scene.add_*` methods for scene assembly.
68
+
69
+ ```python
70
+ from witwin.core import Material, Structure
71
+ from witwin.radar import Scene, Sensor, Simulation
72
+
73
+ scene = Scene(device="cpu").set_sensor(
74
+ origin=(0.0, 0.0, 0.0),
75
+ target=(0.0, 0.0, -1.0),
76
+ up=(0.0, 1.0, 0.0),
77
+ )
78
+
79
+ scene.add_structure(
80
+ Structure(
81
+ name="car_body",
82
+ geometry=car_body_mesh,
83
+ material=Material(eps_r=3.0),
84
+ )
85
+ )
86
+ scene.add_mesh(name="wheel_fl", vertices=wheel_vertices, faces=wheel_faces, dynamic=True)
87
+ scene.add_structure_motion(
88
+ "wheel_fl",
89
+ rotation={
90
+ "axis": (0.0, 1.0, 0.0),
91
+ "angular_velocity": 32.0,
92
+ "origin": (0.0, 0.0, 0.0),
93
+ "space": "local",
94
+ },
95
+ )
96
+
97
+ result = Simulation.mimo(
98
+ scene,
99
+ config=config,
100
+ backend="pytorch",
101
+ sampling="triangle",
102
+ motion_sampling="chirp",
103
+ device="cpu",
104
+ ).run()
105
+ ```
106
+
107
+ Available mutating scene methods:
108
+
109
+ - `Scene.set_sensor(...)`
110
+ - `Scene.add_structure(...)`
111
+ - `Scene.add_mesh(...)`
112
+ - `Scene.add_smpl(...)`
113
+ - `Scene.add_structure_motion(...)`
114
+ - `Scene.update_structure(...)`
115
+ - `Scene.update_structure_motion(...)`
116
+ - `Scene.clear_structure_motion(...)`
117
+
118
+ ## Features
119
+
120
+ - Three solver backends: `pytorch`, `slang`, `dirichlet`
121
+ - Ray tracing through Mitsuba with differentiable scene support
122
+ - Shared-core geometry and structure primitives
123
+ - SMPL body support through `Scene.add_smpl(...)`
124
+ - Optional per-structure rigid motion with parent inheritance
125
+ - Multi-radar orchestration through `Simulation.mimo_group(...)`
126
+ - Torch-native DSP pipeline for range/Doppler processing and point-cloud extraction
127
+ - Optional antenna pattern, polarization, noise-model, and receiver-chain configuration
128
+
129
+ ## Running Tests
130
+
131
+ ```bash
132
+ cd radar
133
+ pytest tests/
134
+ pytest tests/ --gpu
135
+ ```
136
+
137
+ ## Installation
138
+
139
+ Requires Python 3.10+ and a CUDA-capable environment for the CUDA backends.
140
+
141
+ ```bash
142
+ pip install -r requirements.txt
143
+ ```
144
+
145
+ Core dependencies include `torch`, `numpy`, `slangtorch`, `tqdm`, `matplotlib`, and `scipy`. Optional rendering dependencies are `mitsuba` and `drjit`.