How SAS works
Aperture synthesis from first principles, and the sizing rules that follow from it.
A working reference for synthetic aperture sonar geometry, array sizing, and the design rules the simulator's presets follow. Aimed at someone who knows time-domain radar/sonar processing but wants a quick handle on what each SonarCfg field controls and why the canonical values are what they are.
1. Core formulas
| Quantity | Formula | Notes |
|---|---|---|
| Wavelength | λ = sos / fc |
sos = 1500 m/s typical |
| Range resolution | δ_r = sos / (2 · BW) |
independent of range |
| 1-way 3 dB beamwidth (uniform aperture) | θ_3dB ≈ 0.886 · λ / d |
radians, full angle |
| 1-way first-null half-angle | sin θ_null = λ / d |
"FN" = first null |
| Synthetic-aperture length on a target | L_synth = R · θ_tx_bw ≈ R · λ / d_tx_az |
far-field |
| SAS azimuth resolution | δ_az = λ · R / (2 · L_synth) = d_tx_az / 2 |
independent of R and λ |
2. The two design rules
These two rules together determine every meaningful element dimension for a multi-channel SAS array. The simulator's preset registry (appcore/sas_presets.py) enforces both.
2.1 Azimuth resolution: δ_az = d_tx_az / 2
The famous SAS result. Wider TX → finer resolution, because a wider TX is illuminated over a longer along-track span and thus contributes to a longer synthetic aperture. Resolution is independent of range and wavelength.
To set a target resolution (e.g. 2 cm), pick d_tx_az = 0.04 m and stop. The synthetic aperture forms over R · λ / d_tx_az of platform travel at slant range R.
2.2 Azimuth ghost suppression: d_tx_az = channel_spacing
A multi-channel SAS samples along-track at the phase-center pitch, pc_spacing = channel_spacing / 2. Strict Nyquist would require pc_spacing ≤ λ/4, but real arrays have pc_spacing ≫ λ/4. The TX directivity is what saves you: it acts as the anti-alias filter.
The condition for no visible ghosts is:
azimuth alias angle: sin θ_amb = λ / channel_spacing
TX first-null angle: sin θ_null = λ / d_tx_az
require: d_tx_az ≥ channel_spacing
Setting d_tx_az = channel_spacing puts the TX first-null exactly on the alias angle → alias falls in the null → no ghost. Smaller d_tx_az opens the TX beam past the alias and produces visible ghost copies of every target at ±arcsin(λ / channel_spacing) from broadside.
The two rules collapse to: d_tx_az = d_rx_az = channel_spacing (RX elements tile contiguously). Resolution is then channel_spacing / 2.
3. Elevation aperture: keep small
The vertical (elevation) dimension does not benefit from synthetic-aperture processing: the platform doesn't traverse vertically. The simulator uses 5 mm for tx/rx_element_elevation across every preset because:
- At λ ≥ 5 mm (300 kHz or lower),
λ / d_el ≥ 1→ the sinc pattern has no zero at any real angle → no elevation null bands in the image. - The 3 dB beamwidth is wide (≈ 51° at 300 kHz, even wider at 100 kHz) so the entire imaged ground range is illuminated.
If you make d_el larger, you get a tighter vertical beam but the first null arcsin(λ / d_el) falls inside the imaged range and stamps a dark horizontal band in the image. Don't change it unless you are deliberately modelling a different vertical aperture.
4. PRF and range ambiguity
Two-way travel time to the far edge plus the pulse length sets the PRF ceiling:
PRF_max = sos / (2 · (R_far + pulse_length · sos))
The wizard ships PRF = 0.95 · PRF_max (5 % margin). The factor matches appcore/validation/rules._max_range (mirrored by simulator/design_rules.py for the CLI/cloud/studio paths), which is what gates the Run-Simulation button: go any higher and rule R2 fires.
4.1 Platform speed: cap it at what the vehicle can do
The along-track sampling rule ties speed to PRF: the platform may advance at
most half a receive array per ping, speed ≤ ping_advance · PRF with
ping_advance = (n_ch − overlap) · channel_spacing / 2 (0.56 m for HISAS).
The wizard derives the fastest legal speed at the range-limited PRF, which
runs away at short range: 50 m of range gives 11.5 Hz and 6.4 m/s (12.5 kn)
for HISAS, twice a HUGIN's 3.1 m/s maximum. Real operating points (Kongsberg
HISAS 1030 datasheet; Hansen, FFI): 200 m per side at 2 m/s (4 kn), 275 m at
1.5 m/s, altitude ≈ range / 10, about 2 to 2.7 km²/h. Both pairs fall out of
the sampling rule with a 1.2 m array, so the physics is right and only the
speed needs a ceiling.
appcore.sas_presets.SAS_SYSTEM_PLATFORM_SPEED holds that ceiling (HISAS:
2.0 m/s) and derive_sas_geometry(speed_max=...) applies it: when the derived
speed exceeds it, speed is held at the cap and the PRF is lowered to
speed / ping_advance, so the ping advance, the ping count and the image are
unchanged and only the time base becomes realistic. At 200 m the derivation
already lands on 1.9 m/s, so the cap only bites below about 190 m of range.
The HISAS default geometry is 200 m × 80 m, the datasheet operating point.
5. Canonical preset table
What's in appcore/sas_presets.py today:
| System | fc (kHz) | BW (kHz) | n_ch | ch_spc (mm) | d_tx_az / d_rx_az (mm) | d_tx_el / d_rx_el (mm) | δ_az (mm) |
|---|---|---|---|---|---|---|---|
| MUSCLE 300 kHz | 300 | 60 | 36 | 30 | 30 / 30 | 5 / 5 | 15 |
| HISAS 1030 100 kHz | 100 | 30 | 32 | 40 | 40 / 40 | 5 / 5 | 20 |
| Kraken MINSAS 120 (≈337 kHz) | 337 | 40 | 32 | 37.5 | 37.5 / 37.5 | 5 / 5 | 18.75 |
| Klein 5900 600 kHz | 600 | 40 | 28 | 65 | 65 / 65 | 5 / 5 | 32.5 |
Every row satisfies d_tx_az = channel_spacing (rule 2.2 → no ghosts) and δ_az = channel_spacing / 2 (rule 2.1).
6. Failure modes if the rules are violated
| Symptom | Likely cause | Fix |
|---|---|---|
| Bright ghost copies of each target offset ±X m in along-track | d_tx_az < channel_spacing → alias falls inside TX beam |
Set d_tx_az = channel_spacing |
| Coarser resolution than expected (azimuth blur) | TX element narrower or wider than rule 2.1 dictates | Set d_tx_az = 2 · δ_az_target |
| Horizontal dark bands across the image | d_el too large → elevation first-null inside imaged range |
Use d_el = 5 mm for the canonical no-null configuration |
| Run-Simulation button greyed out / R2 fires | PRF over the range-unambiguous budget | Lower PRF to ≤ 0.95 · sos / (2 · (R_far + τ · sos)) |
| Image is mostly empty with bright targets squeezed at top/bottom | range_offset_m mismatch for SAR scenes |
sim.range_offset_m = slant_min; BF reads it back as t0 = 2 · range_offset / sos |
7. Where this is encoded
- Element sizes per system:
appcore/sas_presets.py(preset registry) - Aperture-footprint derivation:
appcore/scene_defaults.py::derive_sas_geometry(uses0.886·λ/channel_spacingfor the synthetic-aperture footprint at the far range) - Beam-pattern interactive viewer: Tools → Beam Pattern Viewer (
gui_imgui/dialogs/beam_pattern.py); sweep element sizes live, plots 1-way TX, 1-way RX, and 2-way TX·RX in azimuth and elevation, with metrics (3 dB BW, first-null) per element
8. References
- Hayes & Gough, Synthetic Aperture Sonar: A Review of Current Status, IEEE J. Ocean. Eng. 34(3), 2009: the textbook treatment of multi-channel SAS Nyquist + resolution.
- Bellettini & Pinto, Design and Experimental Results of a 300-kHz SAS Optimized for Shallow-Water Operations, IEEE JOE 34(3), 2009: MUSCLE/CMRE design rationale.
- Brown et al., PoSSM, POMA 36, 2019: the simulator's point-scattering model.