Skip to content
cfd-lab:~/en/posts/2026-07-24-euler-charact…online
NOTE #113DAY FRI CFD기법DATE 2026.07.24READ 5 min readWORDS 827#Characteristics#Compressible#Euler-Equations#Acoustics#Riemann

Supersonic Flow Can't See Upstream — Characteristics and Sound Waves in the Euler Equations

Why a small disturbance splits into three characteristic speeds

A supersonic fighter jet arrives before the sound of its own engine does. The roar only catches up after it has already passed overhead. Why this happens is, in fact, already written into the Euler equations. This post derives how a small disturbance splits into three separate signals. We'll trace the characteristic curves those signals draw in the (x,t)(x,t) plane (the tracks a signal travels along) in an interactive figure, and finish by pulling out the three characteristic speeds directly in Python.

The compressible equations are signal propagation#

One-dimensional inviscid compressible flow is written as three conservation laws.

tρ+x(ρu)=0\partial_t \rho + \partial_x(\rho u) = 0 t(ρu)+x(ρu2+P)=0\partial_t(\rho u) + \partial_x(\rho u^2 + P) = 0 t(ρetot)+x[(ρetot+P)u]=0\partial_t(\rho e_{\text{tot}}) + \partial_x\big[(\rho e_{\text{tot}} + P)\,u\big] = 0

Here ρ\rho is density, uu is velocity, PP is pressure, and etote_{\text{tot}} is the total specific energy. On the surface these look like equations for a fluid that "flows." But the essence of these equations is not transport — it's signal propagation. All the dynamics of the flow decompose into a handful of signals, each running at a fixed speed. To see what those speeds are, we just need to linearize the equations for a moment.

A small disturbance builds a wave equation#

Lay a very small disturbance on top of a background state (ρ0,u0,P0)(\rho_0, u_0, P_0). Set ρ=ρ0+ρ1\rho = \rho_0 + \rho_1 and u=u0+u1u = u_0 + u_1, use P=KργP = K\rho^\gamma, and keep only first-order terms. Define the derivative that moves with the background, Dtt+u0xD_t \equiv \partial_t + u_0\,\partial_x (the co-moving derivative). Combining the two disturbance equations yields a single wave equation.

Dt2ρ1c02x2ρ1=0D_t^2\,\rho_1 - c_0^2\,\partial_x^2\,\rho_1 = 0

Here c0c_0 is the speed of sound.

c0=γP0ρ0c_0 = \sqrt{\gamma\,\frac{P_0}{\rho_0}}

γ\gamma is the specific-heat ratio. Plugging ρ1ei(kxωt)\rho_1 \sim e^{i(kx-\omega t)} into this equation drops out the dispersion relation.

ωk=u0±c0\frac{\omega}{k} = u_0 \pm c_0

ω\omega is the angular frequency and kk is the wavenumber. The disturbance rides along with the background flow u0u_0 and spreads left and right at the sound speed c0c_0. In other words, a disturbance at one point splits into two speeds: u0+c0u_0 + c_0 and u0c0u_0 - c_0.

Characteristics that split three ways#

The two sound waves aren't the whole story. The third signal is the fluid itself. Suppose we dye the fluid. If at t=0t=0 we color the left side blue and the right side red, the color boundary ϕ\phi is simply carried along with the flow.

tϕ+u0xϕ=0\partial_t \phi + u_0\,\partial_x \phi = 0

This signal moves at speed u0u_0. This is the entropy (or contact) characteristic. To summarize, a one-dimensional flow has three characteristic speeds.

λ=u0c0,λ0=u0,λ+=u0+c0\lambda_- = u_0 - c_0, \qquad \lambda_0 = u_0, \qquad \lambda_+ = u_0 + c_0

The lines in the (x,t)(x,t) plane that satisfy dxdt=λ\dfrac{dx}{dt} = \lambda are called characteristic curves. The λ±\lambda_\pm are the sound waves, and λ0\lambda_0 is the transport of material. Along each acoustic characteristic the Riemann invariant u±2cγ1u \pm \dfrac{2c}{\gamma-1} stays constant, and along the material characteristic the entropy is conserved.

In the animation below, try adjusting the background flow u0u_0 yourself.

The blue tint marks the upstream region the left-going sound has reached. Push u₀ past 1 and the blue tint vanishes: the C− signal now has a positive speed, so every signal runs downstream and the source can no longer talk to what lies upstream.

With M=u0/c0M = u_0/c_0 set to 0.4, the blue signal (the left-running sound wave) stretches to the left of the source — that is, upstream. Raise MM above 1 and that blue track disappears. Because λ=u0c0\lambda_- = u_0 - c_0 has turned positive, all three signals now run only downstream.

Above Mach 1, the cone of influence tips over#

The same story is sharper on a spacetime diagram. The wedge between the three characteristic curves C,C0,C+C_-,\,C_0,\,C_+ emanating from the source is the domain of influence. It's the set of spacetime events the source can send a signal to.

When M<1M<1, this wedge opens on both sides of the vertical line. A signal can travel either upstream or downstream. When M>1M>1, the whole wedge tips over to the right. Even the CC_- characteristic leans to the right, so no upstream event is inside the domain of influence. This is why, in supersonic flow, "upstream can't see downstream." Numerically, this is exactly the reason you must not impose a boundary condition at a supersonic outlet.

In the diagram below, drag the round probe around.

Drag the probe across the plane. At M = 0.4 the wedge straddles the vertical, so events both upstream and downstream are reachable. Slide u₀ above 1 and the whole wedge leans right — drop the probe anywhere to the left of the source and it stays red.

Place the probe inside the wedge and it turns green (reachable); place it outside and it turns red (unreachable). Raise u0u_0 above 1, and no matter where you put the probe to the left of the source, it stays red. Upstream is completely cut off from the signal.

Checking the characteristic speeds in Python#

Let's confirm that the characteristic speeds really are u±cu \pm c and uu. Writing the 1D Euler equations in primitive variables W=(ρ,u,P)W=(\rho,u,P) gives the quasilinear form tW+A(W)xW=0\partial_t W + A(W)\,\partial_x W = 0, and the eigenvalues of the coefficient matrix AA are exactly the characteristic speeds.

import numpy as np
 
def characteristic_speeds(rho, u, p, gamma=1.4):
    """Jacobian A(W) of the 1D Euler primitive variables, W=(rho,u,p).
    Eigenvalues of A = the three characteristic speeds."""
    c = np.sqrt(gamma * p / rho)              # speed of sound
    A = np.array([[u,      rho,        0.0  ],
                  [0.0,    u,          1/rho],
                  [0.0,    rho*c**2,   u    ]])
    lam = np.sort(np.linalg.eigvals(A).real)
    return c, lam
 
rho, u, p = 1.225, 220.0, 1.0e5               # air, u = 220 m/s
c, lam = characteristic_speeds(rho, u, p)
print(f"c0         = {c:8.2f} m/s")
print(f"eig(A)     = {np.round(lam, 2)}")
print(f"u-c,u,u+c  = {np.round([u-c, u, u+c], 2)}")
print(f"Mach       = {u/c:5.3f}  -> {'supersonic' if u > c else 'subsonic'}")

The output is as follows.

c0         =   338.06 m/s
eig(A)     = [-118.06  220.    558.06]
u-c,u,u+c  = [-118.06  220.    558.06]
Mach       =  0.651  -> subsonic

The eigenvalues match uc,u,u+cu-c,\,u,\,u+c exactly. This time, let's actually advance a disturbance in time and measure the speed of its leading front. The acoustic Riemann invariants w±=u1±(c0/ρ0)ρ1w_\pm = u_1 \pm (c_0/\rho_0)\,\rho_1 are scalars carried at u0±c0u_0 \pm c_0 respectively, so we can transport them separately with upwinding.

N, L = 400, 1.0
x = np.linspace(0, L, N, endpoint=False)
dx = L / N
rho0, c0 = 1.0, 1.0
u0 = 0.6 * c0                                  # background Mach 0.6
 
bump = np.exp(-((x - 0.5) ** 2) / (2 * 0.01 ** 2))
wp = bump.copy()                              # right-going invariant (u0 + c0)
wm = bump.copy()                              # left-going  invariant (u0 - c0)
 
def upwind_step(w, a, dt):
    if a > 0:
        dwdx = (w - np.roll(w, 1)) / dx
    else:
        dwdx = (np.roll(w, -1) - w) / dx
    return w - a * dt * dwdx
 
CFL = 0.4
dt = CFL * dx / (abs(u0) + c0)
steps = int(0.25 / dt)
for _ in range(steps):                        # time-advance loop
    wp = upwind_step(wp, u0 + c0, dt)
    wm = upwind_step(wm, u0 - c0, dt)
 
T = steps * dt
def front_speed(w):
    return (x[np.argmax(w)] - 0.5) / T
print(f"u0+c0: target {u0 + c0:+.3f}, measured {front_speed(wp):+.3f}")
print(f"u0-c0: target {u0 - c0:+.3f}, measured {front_speed(wm):+.3f}")
u0+c0: target +1.600, measured +1.600
u0-c0: target -0.400, measured -0.400

The two fronts move at u0+c0u_0+c_0 and u0c0u_0-c_0 respectively. The right-going invariant heads downstream, the left-going invariant heads upstream. Raise u0u_0 above c0c_0 and the measured value on the second line flips positive too.

What the characteristics leave us with#

  • The dynamics of compressible flow are the propagation of signals running at three characteristic speeds, uc, u, u+cu-c,\ u,\ u+c. Two are sound waves, one is the transport of material.
  • The moment the domain-of-influence wedge crosses the vertical line is M=1M=1. Above it, no signal travels upstream.
  • At a supersonic boundary, when all characteristics point one way, the number of boundary conditions you can impose is fixed accordingly. Getting into the habit of counting the signs of the characteristics keeps you from botching boundary conditions.

Share if you found it helpful.