---
title: "Underwater sound propagation"
description: "Closed-form underwater propagation: geometrical spreading plus volume absorption (Francois-Garrison, Ainslie-McColm, Thorp), the speed of sound in sea water (UNESCO/Chen-Millero, Del Grosso, Mackenzie), the sonar equation, seabed reflection loss (Rayleigh) and the ocean ambient-noise spectrum (Wenz wind/thermal plus JOMOPANS-ECHO ship traffic)."
references:
  - type: article
    authors: ["Francois, R. E.", "Garrison, G. R."]
    year: 1982
    title: "Sound absorption based on ocean measurements: Part I: Pure water and magnesium sulfate contributions"
    journal: "The Journal of the Acoustical Society of America"
    volume: 72
    issue: 3
    pages: "896-907"
    doi: "10.1121/1.388170"
    note: "The pure-water and magnesium-sulfate halves of the default absorption model of the transmission-loss section."
  - type: article
    authors: ["Francois, R. E.", "Garrison, G. R."]
    year: 1982
    title: "Sound absorption based on ocean measurements. Part II: Boric acid contribution and equation for total absorption"
    journal: "The Journal of the Acoustical Society of America"
    volume: 72
    issue: 6
    pages: "1879-1890"
    doi: "10.1121/1.388673"
    note: "The boric-acid term and the complete Francois-Garrison total-absorption equation, the implemented default."
  - type: article
    authors: ["Ainslie, M. A.", "McColm, J. G."]
    year: 1998
    title: "A simplified formula for viscous and chemical absorption in sea water"
    journal: "The Journal of the Acoustical Society of America"
    volume: 103
    issue: 3
    pages: "1671-1672"
    doi: "10.1121/1.421258"
    note: "The legible simplified absorption model (\"ainslie-mccolm\")."
  - type: article
    authors: ["Thorp, W. H."]
    year: 1967
    title: "Analytic description of the low-frequency attenuation coefficient"
    journal: "The Journal of the Acoustical Society of America"
    volume: 42
    issue: 1
    pages: "270"
    doi: "10.1121/1.1910566"
    note: "The frequency-only low-frequency absorption formula (\"thorp\")."
  - type: article
    authors: ["Chen, C.-T.", "Millero, F. J."]
    year: 1977
    title: "Speed of sound in seawater at high pressures"
    journal: "The Journal of the Acoustical Society of America"
    volume: 62
    issue: 5
    pages: "1129-1135"
    doi: "10.1121/1.381646"
    note: "The UNESCO international-standard sound-speed equation."
  - type: article
    authors: ["Wong, G. S. K.", "Zhu, S."]
    year: 1995
    title: "Speed of sound in seawater as a function of salinity, temperature, and pressure"
    journal: "The Journal of the Acoustical Society of America"
    volume: 97
    issue: 3
    pages: "1732-1736"
    doi: "10.1121/1.413048"
    note: "The ITS-90 recast of the UNESCO coefficients, the implemented form."
  - type: article
    authors: ["Del Grosso, V. A."]
    year: 1974
    title: "New equation for the speed of sound in natural waters (with comparisons to other equations)"
    journal: "The Journal of the Acoustical Society of America"
    volume: 56
    issue: 4
    pages: "1084-1091"
    doi: "10.1121/1.1903388"
    note: "The alternative pressure-based sound-speed equation (\"del-grosso\")."
  - type: article
    authors: ["Mackenzie, K. V."]
    year: 1981
    title: "Nine-term equation for sound speed in the oceans"
    journal: "The Journal of the Acoustical Society of America"
    volume: 70
    issue: 3
    pages: "807-812"
    doi: "10.1121/1.386920"
    note: "The depth-based nine-term equation and its 1550.744 m/s check value."
  - type: article
    authors: ["Leroy, C. C.", "Parthiot, F."]
    year: 1998
    title: "Depth-pressure relationships in the oceans and seas"
    journal: "The Journal of the Acoustical Society of America"
    volume: 103
    issue: 3
    pages: "1346-1352"
    doi: "10.1121/1.421275"
    note: "The depth-to-pressure conversion feeding the UNESCO and Del Grosso equations."
  - type: book
    authors: ["Urick, R. J."]
    year: 1983
    title: "Principles of underwater sound"
    edition: "3rd ed."
    publisher: "McGraw-Hill"
    note: "Reprinted 1996 by Peninsula Publishing. ISBN 978-0-932146-62-5. Open Library record (https://openlibrary.org/books/OL9317725M). The sonar-equation framework (signal excess, figure of merit)."
  - type: book
    authors: ["Medwin, H.", "Clay, C. S."]
    year: 1998
    title: "Fundamentals of acoustical oceanography"
    publisher: "Academic Press"
    url: "https://shop.elsevier.com/books/fundamentals-of-acoustical-oceanography/medwin/978-0-12-487570-8"
    note: "ISBN 978-0-12-487570-8. The fluid-fluid Rayleigh reflection coefficient and critical grazing angle of the seabed-reflection section."
  - type: article
    authors: ["Wenz, G. M."]
    year: 1962
    title: "Acoustic ambient noise in the ocean: Spectra and sources"
    journal: "The Journal of the Acoustical Society of America"
    volume: 34
    issue: 12
    pages: "1936-1956"
    doi: "10.1121/1.1909155"
    note: "The ambient-noise survey behind the wind and thermal components of the ocean ambient-noise section."
  - type: book
    authors: ["Carey, W. M.", "Evans, R. B."]
    year: 2011
    title: "Ocean ambient noise: Measurement and theory"
    publisher: "Springer"
    doi: "10.1007/978-1-4419-7832-5"
    note: "The wind \"rule of fives\" anchor and the Mellen thermal-noise derivation."
  - type: article
    authors: ["MacGillivray, A.", "de Jong, C."]
    year: 2021
    title: "A reference spectrum model for estimating source levels of marine shipping based on automated identification system data"
    journal: "Journal of Marine Science and Engineering"
    volume: 9
    issue: 4
    pages: "369"
    doi: "10.3390/jmse9040369"
    note: "The JOMOPANS-ECHO ship source-level model (open access); its File S1 calculator is the validation oracle."
  - type: article
    authors: ["Wales, S. C.", "Heitmeyer, R. M."]
    year: 2002
    title: "An ensemble source spectra model for merchant ship-radiated noise"
    journal: "The Journal of the Acoustical Society of America"
    volume: 111
    issue: 3
    pages: "1211-1231"
    doi: "10.1121/1.1427355"
    note: "The ensemble merchant-ship spectrum model of the ship-traffic section."
  - type: book
    authors: ["Jensen, F. B.", "Kuperman, W. A.", "Porter, M. B.", "Schmidt, H."]
    year: 2011
    title: "Computational ocean acoustics"
    edition: "2nd ed."
    publisher: "Springer"
    doi: "10.1007/978-1-4419-8678-8"
    note: "The normal-mode, ray-tracing and split-step Fourier parabolic-equation solvers of the numerical-solvers section."
---

import ThemeImage from '../../../components/ThemeImage.astro';
import Video from '../../../components/Video.astro';

Closed-form underwater propagation, complementing the reference levels of the
[Underwater acoustics](/phonometry/guides/underwater-acoustics/) page: the
**transmission loss**, the **speed of sound** in sea water, the **sonar
equation**, **seabed reflection loss** and the **ocean ambient-noise** spectrum.

## Transmission loss

The transmission loss is

$$
TL = \text{spreading} + \alpha R .
$$

Geometrical spreading is $20 \lg R$ (spherical), $10 \lg R$ (cylindrical) or
spherical up to a transition range $R_0$ and cylindrical beyond it (`"practical"`). The
volume absorption $\alpha$ (dB/km) comes
from **Francois–Garrison** (1982, default and reference), **Ainslie–McColm**
(1998) or **Thorp** (1967, frequency-only); the first two agree to within ~10 %
across 100 Hz–1 MHz.

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/underwater_transmission_loss.svg" alt="Underwater transmission loss versus range at 10 kHz with the geometrical-spreading and volume-absorption contributions drawn separately, loss increasing downward" width="82%" loading="eager" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

# 10 kHz at 10 °C, 35 ppt, 100 m depth; practical spreading with R0 = 1000 m.
ranges = np.linspace(10.0, 20_000.0, 400)
tl = underwater.transmission_loss(ranges, 10e3, law="practical",
                                  transition_range=1000.0, temperature=10.0,
                                  salinity=35.0, depth=100.0)
print(f"alpha = {tl.absorption_coefficient:.2f} dB/km")   # alpha = 0.95 dB/km
tl.plot()   # total TL with the spreading and absorption contributions
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

ranges = np.linspace(10.0, 20_000.0, 400)
tl = underwater.transmission_loss(ranges, 10e3, law="practical", transition_range=1000.0,
                          temperature=10.0, salinity=35.0, depth=100.0)
print(tl.absorption_coefficient, tl.tl[-1])
tl.plot()   # TL vs range with the spreading/absorption split (needs matplotlib)
```

## Speed of sound in sea water

`sea_water_sound_speed(T, S, depth, model=…)` uses the **UNESCO / Chen–Millero**
equation (default, in the Wong & Zhu 1995 ITS-90 form), **Del Grosso** (1974) or
**Mackenzie** (1981). Depth is converted to pressure with Leroy & Parthiot
(1998). The three agree to within ~1 m/s; Mackenzie's canonical check value is
`1550.744 m/s` at 25 °C, 35 ppt, 1000 m.

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/underwater_sound_speed.svg" alt="A sea-water sound-speed profile from the UNESCO equation: warm mixed layer, thermocline, a sound-channel axis at the minimum, and the speed rising with pressure at depth" width="60%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

# A warm mixed layer, a thermocline down to 4 °C and an isothermal deep layer.
depths = np.linspace(0.0, 3000.0, 121)
temps = 4.0 + 14.0 / (1.0 + (np.maximum(depths - 80.0, 0.0) / 250.0) ** 2)
profile = underwater.sound_speed_profile(depths, temps, 35.0, model="unesco")
profile.plot()   # sound speed vs depth, minimum at the sound-channel axis
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

c = underwater.sea_water_sound_speed(25.0, 35.0, 1000.0, model="mackenzie")  # 1550.744
depths = np.linspace(0.0, 3000.0, 121)
temps = 4.0 + 14.0 / (1.0 + (np.maximum(depths - 80.0, 0.0) / 250.0) ** 2)
profile = underwater.sound_speed_profile(depths, temps, 35.0, model="unesco")
profile.plot()   # sound speed vs depth (needs matplotlib)
```

The $c(z)$ minimum acts as a waveguide (the SOFAR channel): wavefronts that
stray from the axis are refracted back toward it, while sound generated
outside the channel leaks away to depth, as the simulation below shows with an
intentionally exaggerated gradient. This trapping is why low-frequency sound
can cross entire oceans.

<Video
  name="anim_fdtd_ducting"
  width={2400}
  height={1350}
  title="Sound trapped in a SOFAR channel, 2D FDTD simulation"
  description="Two 2D FDTD runs of a low-frequency pulse in a SOFAR-like underwater sound channel, with the c(z) profile drawn beside each field. Launched on the channel axis at 400 m depth the wavefronts keep refracting back toward the sound-speed minimum and stay trapped; launched near the surface at 150 m the energy crosses the channel and leaks away to depth. The closing seconds fade to the time-integrated energy map, showing the whole path history of each run."
/>

## Sonar equation

The sonar equation gives the **signal excess** $SE$ (detection when
$SE \ge 0$) and the **figure of merit** (the maximum allowable transmission
loss at $SE = 0$):

$$
SE = SL - TL - (NL - DI) - DT \ \ \text{(passive)},
$$

$$
SE = SL - 2\,TL + TS - (NL - DI) - DT \ \ \text{(active, monostatic)},
$$

or reverberation-limited with $RL$ in place of $NL - DI$.

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/sonar_equation.svg" alt="The passive sonar equation: signal excess falling with transmission loss and crossing zero, the detection limit, at the figure of merit" width="82%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

tl = np.linspace(40.0, 120.0, 400)
se = underwater.passive_sonar_equation(source_level=140.0, transmission_loss=tl,
                                       noise_level=60.0, directivity_index=15.0,
                                       detection_threshold=8.0)
print(f"figure of merit = {se.figure_of_merit:.1f} dB")  # figure of merit = 87.0 dB
se.plot()   # signal excess vs transmission loss, zero crossing at the FOM
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

tl = np.linspace(40.0, 120.0, 400)
se = underwater.passive_sonar_equation(source_level=140.0, transmission_loss=tl,
                               noise_level=60.0, directivity_index=15.0,
                               detection_threshold=8.0)
print(se.figure_of_merit)
se.plot()   # signal excess vs transmission loss (needs matplotlib)
```

Sonar, propagation and ambient levels are in dB re a plane wave of 1 µPa rms
(spectrum levels). Source levels (below) use the source convention, dB re
1 µPa²/Hz **at 1 m**.

## Seabed reflection loss

A plane wave striking the seabed reflects with the fluid–fluid **Rayleigh
reflection coefficient** (Medwin & Clay). For a faster bottom ($c_2 > c_1$) there is a **critical
grazing angle** $\varphi_c = \arccos(c_1/c_2)$, below which the wave is
totally reflected ($|R| = 1$, zero loss). The bottom loss is
$BL = -20 \lg |R|$.

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/seabed_reflection.svg" alt="Bottom reflection loss versus grazing angle for a fast sandy seabed: zero loss below the critical grazing angle, rising sharply above it" width="82%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

# Rayleigh fluid-fluid reflection: water over a fast sandy bottom.
phi = np.linspace(0.0, 90.0, 361)
bl = underwater.bottom_reflection_loss(phi, rho1=1000.0, c1=1500.0,
                                       rho2=1900.0, c2=1650.0)
print(f"critical angle = {bl.critical_angle:.1f} deg")   # critical angle = 24.6 deg
bl.plot()   # bottom loss vs grazing angle
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

phi = np.linspace(0.0, 90.0, 361)   # grazing angle from the interface, degrees
bl = underwater.bottom_reflection_loss(phi, rho1=1000.0, c1=1500.0,   # water
                               rho2=1900.0, c2=1650.0)          # sand
print(bl.critical_angle)            # 24.6°
bl.plot()   # bottom loss vs grazing angle (needs matplotlib)
```

The companion `seabed_reflection` bundles the complex `reflection_coefficient`,
its `magnitude` $|R|$, the `bottom_loss` (dB) and the interface parameters into a
`SeabedReflection` whose `.plot()` draws the reflection-coefficient magnitude
$|R|$ directly (unity below the critical angle, dropping to the normal-incidence
value at $90°$).

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/seabed_reflection_coefficient.svg" alt="Seabed reflection-coefficient magnitude versus grazing angle for a fast sandy seabed: total reflection below the critical grazing angle, falling to the normal-incidence value above it" width="82%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

# Rayleigh reflection-coefficient magnitude: water over a fast sandy bottom.
phi = np.linspace(0.0, 90.0, 361)
sr = underwater.seabed_reflection(phi, rho1=1000.0, c1=1500.0,
                                  rho2=1900.0, c2=1650.0)
print(f"|R| at normal incidence = {sr.magnitude[-1]:.3f}")  # 0.353
sr.plot()   # reflection-coefficient magnitude vs grazing angle
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

phi = np.linspace(0.0, 90.0, 361)   # grazing angle from the interface, degrees
sr = underwater.seabed_reflection(phi, rho1=1000.0, c1=1500.0,   # water
                          rho2=1900.0, c2=1650.0)          # sand
print(sr.magnitude[-1])             # 0.353 = |R| at normal incidence
sr.plot()   # |R| vs grazing angle (needs matplotlib)
```

## Ocean ambient noise

The ambient-noise spectrum level is the energy sum of the physically grounded
Wenz components: **wind / sea-surface** noise via the "rule of fives" (the
historical 25 dB anchor at 1 kHz for 5 knots is re 20 µPa, i.e. ~51 dB re 1 µPa;
strictly valid ~500 Hz–5 kHz) and **Mellen thermal** noise
(dominant above ~50 kHz). The wide example range keeps the wind curve plotted
beyond ~5 kHz only as an extrapolation to show the thermal crossover. A
**shipping** spectrum may be supplied by the caller.

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/ocean_ambient_noise.svg" alt="Wenz ambient-noise spectrum levels for two wind speeds, with wind noise falling at 5 dB per octave and thermal noise rising above about 50 kHz" width="82%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

# Wenz ambient noise (wind rule of fives + Mellen thermal) at two wind speeds.
freqs = np.logspace(2, 5.5, 300)
fig, ax = plt.subplots()
for u in (5.0, 20.0):
    noise = underwater.ocean_ambient_noise(freqs, wind_speed_knots=u)
    ax.semilogx(noise.frequency, noise.spectrum_level, label=f"Total ({u:.0f} kn)")
ax.semilogx(freqs, underwater.thermal_noise_spectrum(freqs), ":", label="Thermal")
ax.set(xlabel="Frequency [Hz]", ylabel="Spectrum level [dB re 1 µPa²/Hz]")
ax.legend()
ax.grid(True, which="both", alpha=0.3)
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

freqs = np.logspace(2, 5.5, 300)
noise = underwater.ocean_ambient_noise(freqs, wind_speed_knots=15.0)
noise.plot()   # composite spectrum with wind/thermal components (needs matplotlib)
```

## Ship-traffic source level

When no measured spectrum is available, a ship's source level can be
**estimated** from its class, speed and length with **JOMOPANS-ECHO**
(MacGillivray & de Jong 2021, the default, validated against 1862 measurements),
**RANDI 3.1** or **Wales & Heitmeyer** (2002).

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/ship_traffic_noise.svg" alt="JOMOPANS-ECHO predicted source-level spectra for a container ship, a cruise ship and a tug, with cargo vessels showing a low-frequency hump below 100 Hz" width="82%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
from phonometry import underwater

# JOMOPANS-ECHO source spectra for three vessel classes (speed, length).
fig, ax = plt.subplots()
for vessel_class, speed, length in (("containership", 18.0, 300.0),
                                    ("cruise", 17.1, 250.0),
                                    ("tug", 3.7, 30.0)):
    s = underwater.ship_source_spectrum(speed, length, vessel_class=vessel_class)
    ax.semilogx(s.frequency, s.source_psd,
                label=f"{vessel_class} ({speed:.0f} kn, {length:.0f} m)")
ax.set(xlabel="Frequency [Hz]",
       ylabel="Source spectral density [dB re 1 µPa²/Hz at 1 m]")
ax.legend()
ax.grid(True, which="both", alpha=0.3)
plt.show()
```

</details>

```python
from phonometry import underwater

ship = underwater.ship_source_spectrum(18.0, 300.0, vessel_class="containership")
ship.plot()                     # source spectral density vs frequency
print(underwater.VESSEL_CLASSES)        # the 13 JOMOPANS-ECHO vessel classes

# Feed the prediction into the ambient noise as the shipping term:
noise = underwater.ocean_ambient_noise(ship.frequency, wind_speed_knots=10.0,
                               shipping=ship.source_psd)
```

## Numerical solvers

For range-independent environments the field can be computed numerically with
three solvers (Jensen et al., *Computational Ocean Acoustics*):

- **`normal_modes`**: the depth-separated Sturm-Liouville eigenproblem solved by
  finite differences, summed into transmission loss (validated against the ideal
  waveguide's exact modes).
- **`ray_trace`**: the ray-trajectory equations integrated with Runge-Kutta,
  vectorised over all rays at once (validated against a linear gradient's
  circular arcs).
- **`parabolic_equation`**: the standard (Tappert) PE via the split-step Fourier
  algorithm (validated against free-field spherical spreading).

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/numerical_propagation.webp" alt="A Munk sound-speed profile, ray paths forming convergence zones, and normal-mode versus parabolic-equation transmission loss agreeing in trend" width="100%" />

<details>
<summary>Show the code for this figure</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import underwater

# A Munk deep-water sound-speed profile.
z = np.linspace(0.0, 5000.0, 60)
eta = 2.0 * (z - 1300.0) / 1300.0
c = 1500.0 * (1.0 + 0.00737 * (eta - 1.0 + np.exp(-eta)))

# Split-step Fourier PE at 50 Hz; a coarse grid keeps the run fast.
field = underwater.parabolic_equation(50.0, z, c, source_depth=1000.0,
                                      max_range=50_000.0, range_step=50.0,
                                      n_depth_points=512)
field.plot()   # TL(z, r) field showing the convergence zones
plt.show()
```

</details>

```python
import numpy as np
from phonometry import underwater

z = np.linspace(0.0, 5000.0, 60)
eta = 2.0 * (z - 1300.0) / 1300.0
c = 1500.0 * (1.0 + 0.00737 * (eta - 1.0 + np.exp(-eta)))   # Munk profile
underwater.ray_trace(z, c, source_depth=1000.0,
             launch_angles_deg=np.linspace(-12, 12, 21), max_range=100e3).plot()

modes = underwater.normal_modes(50.0, [0.0, 200.0], [1500.0, 1500.0],
                        source_depth=50.0, receiver_depth=100.0)
underwater.parabolic_equation(50.0, [0.0, 200.0], [1500.0, 1500.0],
                      source_depth=50.0, max_range=20e3).plot()
```

All three assume a range-independent water column with a pressure-release
surface.

## See also

- API reference: [`underwater.propagation`](/phonometry/reference/api/underwater/propagation/), [`underwater.numerical_propagation`](/phonometry/reference/api/underwater/numerical-propagation/) and [`underwater.sound_speed`](/phonometry/reference/api/underwater/sound-speed/).
