A sonar simulator is easy to write and hard to trust. The usual failure is a renderer that produces plausible-looking speckle with no defensible relationship to acoustics. ApertureLab carries two independent forward models built on different mathematics, writing the same output format, and holds them against each other and against a published result.
Backend one
The seafloor and every object on it become millions of individual reflectors. For each transmitted pulse, and for every reflector and receive element, the GPU computes the travel time, the returned amplitude, and whether anything blocks the line of sight, then sums those echoes into a raw time series.
Amplitude follows a Lambert scattering law modulated by the transducer beam pattern and seawater absorption (Thorp, per Urick Eq. 5.18); the scattering model implements PoSSM (Brown et al., 2017 and 2019). Because it tracks individual reflectors, this backend handles anything: curved tracks, circular collections, arbitrary geometry, six-degree-of-freedom platform motion.
Implementation: simulator/gpu_raytrace.py,
simulator/gpu_echo.py.
Backend two
Instead of tracking reflectors one at a time, the Fourier-wavefield backend renders the scene once as a depth image, converts that picture into a spatial-frequency spectrum, and applies one exact change of variables, the Stolt mapping, to turn what the scene looks like into what the sonar would have recorded.
Its cost does not depend on the number of scatterers, so it wins on very dense scenes where the point-scatterer method is counting grains of sand. It implements Sanford, Thomas and Hunter, IEEE Journal of Oceanic Engineering 49(4):1501-1515, 2024, in the paper's four steps.
Implementation: simulator/fw_engine.py,
simulator/rasterizer.py.
The argument
Two forward models sharing no mathematics and no code path should not agree by accident. Across a twelve-point range sweep, the Fourier-wavefield and point-scatterer backends land on the same peak pixel at every point, with peak levels within half a decibel and resolution within one percent.
Separately, the simulator reproduces Figure 3 of the Sanford 2024 paper with pass/fail gates in the test suite, so a regression that breaks the replication breaks the build rather than quietly degrading the imagery.
Both engines write the identical HDF5 layout, so nothing downstream knows or cares which one produced a file.
One engine, several instruments
The sonar is a parameter, not an assumption. Six named systems ship with geometry derived from their datasheets, and beamwidths, pulse repetition frequency, and element sizes are computed from physics rather than hardcoded. The same scene can be rendered as a synthetic-aperture collection, as a real-aperture side-scan line, or, changing only the band, as airborne Ka-band radar at 16.7 GHz through the same back-projection beamformer.
That makes cross-modality transfer a property of the scene file rather than a separate research project: the same ground truth, the same geometry, three sensors.
Appendix
This is the acoustics rather than the summary; the observation model, the wavefront solver, the amplitude terms, shadowing, speckle statistics, calibration, and the GPU kernel that evaluates it.
A PoSSM-style coherent point-scatterer simulator; every ping is a time-domain sum of per-scatterer echoes, each carrying its own delay, amplitude, and carrier phase.
For ping \(k\) on channel \(c\), the complex baseband pressure is a coherent sum over scatterers \(i\):
\[ s_{k,c}(t) \;=\; \sum_{i} a_{k,c,i}(t)\;p\!\left(t - \tau_{k,c,i}(t)\right)\;\exp\!\left(-j\,2\pi f_c\,\tau_{k,c,i}(t)\right), \]where \(p(\cdot)\) is the baseband LFM chirp, \(f_c\) is the carrier (300 kHz default), and
\[ \tau_{k,c,i}(t) \;=\; \frac{\lVert \mathbf{x}_i - \mathbf{x}^{TX}_k(t)\rVert \;+\; \lVert \mathbf{x}_i - \mathbf{x}^{RX}_{k,c}(t)\rVert}{c_s} \]is the bistatic two-way time between the TX phase center and the \(c\)-th RX element. Production runs are continuous-motion: the RX position is evaluated at the echo arrival time, not at ping start. Stop-and-hop is disabled by default and is forbidden in production imaging.
The bistatic arrival time \(\tau\) is not a closed-form quantity, because the receiver moves while the echo is in flight. The true \(\tau\) satisfies the implicit equation
\[ \tau \;=\; \frac{d_{TX} \;+\; \bigl\lVert \mathbf{x}_i - \mathbf{x}^{RX}(\tau)\bigr\rVert}{c_s}, \qquad \mathbf{x}^{RX}(\tau) \;=\; \mathbf{x}^{RX}_0 + \mathbf{v}\,\tau, \]i.e. \(\tau\) is the time at which the outgoing wavefront, after bouncing off the scatterer, intersects the receiver's straight-line trajectory. With the closed-form expansion of \(d_{RX}(\tau)^2\) along the trajectory,
\[ d_{RX}(\tau)^{2} \;=\; d_{RX}(0)^{2} \;-\; 2\,\tau\,\bigl(\mathbf{v}\!\cdot\!\Delta\mathbf{x}_{RX}\bigr) \;+\; \tau^{2}\,\lVert\mathbf{v}\rVert^{2}, \qquad \Delta\mathbf{x}_{RX} = \mathbf{x}_i - \mathbf{x}^{RX}_0, \]the implicit equation becomes a Picard fixed-point iteration that we solve directly on the GPU:
\[ \tau^{(n+1)} \;=\; \frac{d_{TX} \;+\; \sqrt{\,d_{RX}(0)^{2} \;-\; 2\,\tau^{(n)}\,(\mathbf{v}\!\cdot\!\Delta\mathbf{x}_{RX}) \;+\; \tau^{(n)\,2}\,\lVert\mathbf{v}\rVert^{2}\,}}{c_s}, \qquad \tau^{(0)} \;=\; \frac{d_{TX}+d_{RX}(0)}{c_s}. \]The initial guess \(\tau^{(0)}\) is exactly the stop-and-hop delay. Each iteration is a contraction with rate \(\sim\!\lVert\mathbf{v}\rVert/c_s\): about \(10^{-3}\) at SAS speeds: so one iteration drops the residual TOF error from \(O(v R / c_s^{2})\) (sub-cm at 100 m range) into the noise floor of the FP64 carrier phase. The default is one iteration (\(\mathtt{PS\_N\_TOF\_ITERS}=1\)), chosen not for speed but for an exact match to the beamformer's \(\mathtt{n\_tof\_iters}=1\) MoComp model: sim and BF use the same \(\tau\), so any residual phase error cancels coherently in TDBP rather than smearing the PSF.
The signature of getting this wrong is the asymmetric along-track sidelobe pattern of Cook & Brown (2009): the linear-phase-error fingerprint of a stop-and-hop sim driven through a continuous-motion beamformer. The solver above is what keeps it out of the imagery.
The complex amplitude \(a_{k,c,i}\) factorizes (linear pressure, not intensity):
\[ a_{k,c,i} \;=\; \rho_i \cdot \underbrace{B_{TX}(\hat{\mathbf{u}}^{TX}_i)\,B_{RX}(\hat{\mathbf{u}}^{RX}_{c,i})}_{\text{rectangular-piston sinc}}\;\cdot\;\underbrace{\cos\theta_i^{n}}_{\text{Lambert}}\;\cdot\;\underbrace{\frac{1}{d_{TX,i}\,d_{RX,c,i}}}_{\text{two-way spreading}}\;\cdot\;\underbrace{e^{-\alpha(f_c)\,(d_{TX,i}+d_{RX,c,i})}}_{\text{Thorp absorption}}\;\cdot\;V_i. \]Each piston is a separable 2D sinc keyed on body-frame direction cosines, \(B(\hat{\mathbf{u}}) = |\mathrm{sinc}(\pi L_{az} u_x / \lambda)|\cdot|\mathrm{sinc}(\pi L_{el} u_z / \lambda)|\); attitude (R/P/Y) rotates the beam through \(R_{NED \to body}\). \(\cos\theta_i^{n} = |\hat{\mathbf{u}}^{TX}_i \!\cdot\! \hat{\mathbf{n}}_i|\) uses the heightfield surface normal at the scatterer: this is the Lambert directivity of Brown et al. 2019 (PoSSM). Per-scatterer reflectivities \(\rho_i = |\rho_i|\,e^{j\phi_i}\) carry Rayleigh-distributed magnitude with uniform phase. Absorption \(\alpha(f_c)\) is the Thorp seawater formula (~0.067 dB/m at 300 kHz).
The visibility factor \(V_i \in \{0,1\}\) (or a soft Fresnel knife-edge value in \([0,1]\) when enabled) comes from a DDA ray-march of the TX-to-scatterer segment against the heightfield: the ray is occluded at the first cell where \(z_{ray} > z_{terrain}\). Surface normals \(\hat{\mathbf{n}}_i\) are read from the same heightfield, clamped to a 60° max-slope so single-cell object edges cannot mute their surroundings.
Scatterers are placed uniformly on the heightfield surface (with sub-wavelength normal-aligned jitter to avoid coherent halos). Per Goodman, fully-developed speckle requires \(N_{cell} \gg 1\) random complex scatterers per resolution cell; the bottom-type amplitude is calibrated by
\[ \sigma_z = \sqrt{\frac{\sigma_{bs}(20^\circ)}{2\,\rho\,\sin^2 20^\circ}}, \qquad \mathbb{E}[|\rho_i|] = \sigma_z\sqrt{\pi/2}, \]where \(\rho\) is the scatterer density (m\(^{-2}\)) and \(\sigma_{bs}(20^\circ)\) is the APL-UW backscattering strength (e.g. \(-28\) dB for sand). The amplitude scaling absorbs \(\rho\) so the rendered intensity is density-invariant; \(\rho = 2000\)/m\(^2\) is the production working point, \(\rho = 200\)/m\(^2\) for debug.
A deterministic calibration constant \(K_{PS}\) ties the simulator output to a closed-form analytic prediction for a known point target:
\[ |\text{peak}|^2_{\text{dB}} \;=\; TS \;-\; 40\log_{10}R \;-\; 2\alpha R \;+\; 10\log_{10}(BT), \]which combines two-way spreading, Thorp absorption, and the LFM matched-filter coherent gain \(10\log_{10}(BT)\). The calibration harness drives a single target through the full pipeline and asserts agreement to within ±1 dB.
One CUDA thread per \((c, i)\) pair computes the carrier rotation \(e^{-j2\pi f_c \tau}\) in FP64, multiplies the pre-sampled chirp, and atomicAdds the result into an upsampled \((n_{ch}, K\!\cdot\!n_{samp})\) accumulator. The buffer is then FFT-domain low-passed and decimated to \(f_s\): the oversampling preserves sub-sample delay precision that would otherwise quantize to \(\pm 1/(2 f_s)\) range bins.