---
title: "Atmospheric refraction: rays and the parabolic equation"
description: "Ray tracing and the Green's Function parabolic equation (GFPE) for a refracting atmosphere: effective sound-speed profiles, curved rays with closed-form curvature radius and shadow-zone distance, and the relative-level field over the range-height plane, anchored to the spherical ground effect in the homogeneous limit."
references:
  - type: book
    authors: ["Salomons, E. M."]
    year: 2001
    title: "Computational atmospheric acoustics"
    publisher: "Kluwer Academic"
    doi: "10.1007/978-94-010-0660-6"
    note: "Chapter 4 (effective sound speed, the ray model and PE examples), Appendix G (the Crank-Nicholson PE and the Gaussian starter, Eqs. G.64, G.76) and Appendix H (the Green's Function PE, the impedance reflection Eq. H.28 and the refraction factor Eq. H.58). ISBN 978-1-4020-0390-5."
  - type: book
    authors: ["Attenborough, K.", "Van Renterghem, T."]
    year: 2021
    title: "Predicting outdoor sound"
    edition: "2nd ed."
    publisher: "CRC Press"
    doi: "10.1201/9780429470141"
    note: "Chapter 11 (refraction by wind and temperature gradients, ray models and shadow zones). ISBN 978-1-138-30655-2."
---

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

The [ISO 9613-2 method](/phonometry/guides/outdoor-propagation/) and the
[spherical-wave ground effect](/phonometry/guides/ground-barriers/) both assume
a **homogeneous** atmosphere. In reality the sound speed changes with height,
because temperature and wind change with height, and this **refracts** sound:
rays curve, and over a few hundred metres the received level can swing by tens
of decibels. This page covers
`phonometry.environmental.atmospheric_refraction`, the refracting-atmosphere
counterpart of the ocean solvers in
[`phonometry.underwater.numerical_propagation`](/phonometry/guides/underwater-propagation/):
a **ray model** and a **parabolic-equation (PE)** solver.

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/atmospheric_refraction.webp" alt="Two stacked panels for an upward-refracting atmosphere over grassland. Top: a fan of sound rays leaving a source 2 m above the ground curves upward, so beyond a dashed shadow-zone boundary near 110 m no ray stays close to the ground. Bottom: the GFPE relative-level field over range and height shows red interference lobes near the source and a deep blue acoustic shadow spreading up and to the right, its edge tracking the same boundary." width="88%" loading="eager" />

## 1. Effective sound-speed profile

A moving (windy) atmosphere is well approximated by a non-moving one with the
**effective sound speed** `c_eff(z) = c(z) + u(z)`, the adiabatic sound speed
plus the component of the wind in the propagation direction (Salomons Eq. 4.4).
Two profile shapes cover most surface-layer cases:

- a **linear** profile `c_eff(z) = c0 + g·z` (`linear_sound_speed_profile`),
  the simplest refracting atmosphere and the one with exact ray geometry;
- the realistic **logarithmic** surface-layer profile
  `c_eff(z) = c0 + b·ln(1 + z/z0)` (`log_linear_sound_speed_profile`, Salomons
  Eq. 4.5), with `b ≈ +1 m/s` for a typical downward-refracting atmosphere,
  `b ≈ -1 m/s` for an upward-refracting one, and `z0` the roughness length
  (about 0.1 m for grassland).

A positive gradient (sound speed increasing upward) bends rays **down** toward
the receiver (favourable propagation); a negative gradient bends them **up** and
opens an acoustic **shadow** near the ground.

```python
from phonometry import log_linear_sound_speed_profile

profile = log_linear_sound_speed_profile(-1.0, ground_speed=340.0)  # upward
profile.speed_at(10.0)   # effective sound speed 10 m above the ground
```

## 2. Ray model

In geometrical acoustics a sound ray obeys Snell's law
`cos(γ(z))/c(z) = const` (Salomons Eq. 4.3). `atmospheric_ray_paths` integrates
it with a fourth-order Runge-Kutta scheme, marching in range and reflecting
specularly at the ground, and returns the curved paths, the turning points, the
travel times and the number of ground reflections.

```python
import numpy as np
from phonometry import atmospheric_ray_paths, log_linear_sound_speed_profile

profile = log_linear_sound_speed_profile(-1.0, ground_speed=340.0)
rays = atmospheric_ray_paths(profile, source_height=2.0,
                             launch_angles_deg=np.linspace(-8.0, 8.0, 17),
                             max_range=600.0)
rays.turning_points      # turning points per ray
```

### Closed-form geometry (linear gradient)

For a **linear** profile every ray is an exact **circular arc** of radius of
curvature (Salomons Sec. 4.4; Attenborough Ch. 11):

$$
R_c = \frac{c_0}{|g|\cos\theta_0},
$$

exposed as `ray_curvature_radius`. A ray launched at angle `θ0` in downward
refraction turns at the height `Rc(1 - cos θ0)`. For an **upward-refracting**
linear profile the ground-grazing ray bounds a region beyond which no direct or
once-reflected ray arrives, at the closed-form **shadow-zone distance**
(`shadow_zone_distance`):

$$
x_\text{shadow} = \sqrt{2 R_c}\left(\sqrt{h_s} + \sqrt{h_r}\right),
\qquad R_c = \frac{c_0}{|g|}.
$$

These closed forms are the exact oracle for the ray tracer: a circle fit of a
traced ray recovers `Rc` to machine precision.

## 3. Parabolic equation (Green's Function PE)

The **parabolic equation** is the reference method for refraction and shadow
zones at long range. It replaces the wave equation with a one-way (outgoing)
equation valid within a limiting elevation angle, and marches it in range on a
range-height grid. `atmospheric_parabolic_equation` implements the **Green's
Function PE** (GFPE, Salomons Appendix H), the atmospheric member of the same
split-step Fourier family as the ocean
[`parabolic_equation`](/phonometry/guides/underwater-propagation/). Each range
step transforms the field to the vertical-wavenumber domain, applies the
free-space propagator together with the finite-impedance ground reflection
`R(kz) = (kz Z − k0)/(kz Z + k0)` (Salomons Eq. H.28), transforms back, adds the
surface-wave residue of the reflection pole (the third term of Eq. H.49) and
applies the refraction phase screen `exp(i Δr (k(z) − ka))` (Eq. H.58). The
source is a **Gaussian starter** with its ground image (Eqs. G.64, G.76) and an
**absorbing layer** at the top of the grid suppresses top-boundary reflections.
The result is the **relative sound level** `ΔL(z, r) = 20 lg(|p| R1)` (dB re
free field) over the whole range-height plane.

```python
from phonometry import atmospheric_parabolic_equation, log_linear_sound_speed_profile

profile = log_linear_sound_speed_profile(-1.0, ground_speed=340.0)  # upward
pe = atmospheric_parabolic_equation(400.0, profile, source_height=2.0,
                                    flow_resistivity=200e3,  # grassland
                                    max_range=600.0, max_height=40.0)
pe.level_at_height(2.0)   # relative level vs range at 2 m
```

The ground impedance is supplied directly (`impedance=`, a normalized complex
value in the `e^{-iωt}` convention of Salomons, with `Im(Z) > 0` for a passive
ground), as a `PorousMediumResult`, or from an effective `flow_resistivity` via
the [porous models](/phonometry/guides/porous-absorbers/) of the materials
domain; the porous models work in the opposite `e^{+jωt}` convention, so their
impedance is conjugated internally.

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

```python
import warnings

import matplotlib.pyplot as plt
import numpy as np

from phonometry import (
    atmospheric_parabolic_equation,
    atmospheric_ray_paths,
    log_linear_sound_speed_profile,
    shadow_zone_distance,
)

c0 = 340.0
profile = log_linear_sound_speed_profile(-1.0, ground_speed=c0, max_height=60.0)
zs = 2.0
fig, axes = plt.subplots(2, 1, figsize=(11, 8.2), sharex=True)

rays = atmospheric_ray_paths(profile, source_height=zs,
                             launch_angles_deg=np.linspace(-8.0, 8.0, 17),
                             max_range=600.0, n_steps=3000)
for i in range(rays.heights.shape[0]):
    axes[0].plot(rays.ranges[i], rays.heights[i], color="#1f77b4", lw=0.8, alpha=0.7)
axes[0].plot([0.0], [zs], "o", color="#d62728", label="Source")
grad = (profile.speed_at(10.0) - c0) / 10.0
x_sh = shadow_zone_distance(float(grad), zs, zs, ground_speed=c0)
axes[0].axvline(x_sh, color="#d62728", ls="--", label="Shadow-zone boundary")
axes[0].set(ylabel="Height [m]", ylim=(0, 40), title="Sound rays (upward refraction)")
axes[0].legend()

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    pe = atmospheric_parabolic_equation(400.0, profile, source_height=zs,
                                        flow_resistivity=200e3,
                                        max_range=600.0, max_height=40.0)
img = axes[1].imshow(pe.relative_level, cmap="RdBu_r", vmin=-30, vmax=6,
                     aspect="auto", origin="lower", interpolation="bilinear",
                     extent=(pe.ranges[0], pe.ranges[-1], pe.heights[0], pe.heights[-1]))
axes[1].axvline(x_sh, color="k", ls="--")
axes[1].set(ylabel="Height [m]", xlabel="Range [m]", ylim=(0, 40),
            title="GFPE relative sound level")
fig.colorbar(img, ax=axes[1], label="Level re free field [dB]")
plt.tight_layout()
plt.show()
```

</details>

## 4. Validation

The models are anchored by independent oracles, pinned numerically in the
[conformance report](/phonometry/reference/conformance/) (section "Atmospheric
refraction"):

- **Homogeneous limit → spherical ground effect.** With a zero gradient the
  GFPE field reproduces the exact Weyl-Van der Pol
  [`ground_effect`](/phonometry/guides/ground-barriers/) at every range, to a
  few tenths of a dB over grassland on the default grid, and to the coherent
  **+6 dB** two-ray enhancement over a rigid ground.
- **Exact ray geometry.** For a linear profile the traced ray is a circular arc
  whose fitted radius matches `ray_curvature_radius` to machine precision.
- **Reciprocity.** Swapping the source and receiver heights leaves the PE level.
- **Shadow zone.** Over an upward-refracting profile the PE level collapses far
  inside the closed-form shadow distance.
