Skip to content
cfd-lab:~/en/posts/2026-08-04-nscbc-nonrefl…● online

Translated from the author's Korean original. Read the original (한국어)

NOTE #122DAY TUE 유체역학DATE 2026.08.04UPDATED 2026.10.19READ 9 min read#NSCBC#Boundary-Condition#Acoustics#Compressible#Characteristics

The Outlet Is Open, Yet the Wave Comes Back — NSCBC and the Price of σ

Zero reflection does not hold the mean pressure. Past a point, raising σ loses the offset as well as the reflection target.

The outlet is open. The wave comes back anyway. Handle the outflow boundary of a compressible code with a casual extrapolation, run a flame or a turbulent patch, and an oscillation with no physical cause grows in the middle of the domain. Measure its period and it usually matches the domain length divided by the speed of sound. Your computational box has quietly become a resonator. Split what that boundary can compute from what it has to invent. The coefficient that moves the invented part has a price, and the price is in the table.

Drag the Mach slider from a positive subsonic value through sonic. Incoming arrows leave. Flip the sign negative and the condition count on that same face jumps.

boundary

A subsonic outlet carries one condition. Past sonic the count is zero. Flip the face into an inlet and it becomes four. A wave is an invented number only while its arrow points into the box.

The Edge of the Box Is Not Physics#

A physical domain has no edge. Space continues past the combustor exit. A grid, though, has to stop somewhere, and the last cell has no neighbor. Without a neighbor there is no derivative, and without a derivative there is no governing equation to advance.

In RANS codes the problem stayed hidden for a long time. Turbulent and artificial viscosity are large, so a badly manufactured wave at the boundary dies within a few cells. LES and DNS change the accounting. Artificial viscosity is near zero and turbulent viscosity is at a minimum. Whatever error the boundary creates does not dissipate — it crosses the domain and comes back.

The recipe Poinsot and Lele assembled in 1992 inverts the approach. Instead of extrapolating variables at the boundary, you count the waves crossing it and fix each amplitude. It extends the Euler characteristic boundary conditions (ECBC) to Navier–Stokes with its viscous terms, hence NSCBC — Navier–Stokes Characteristic Boundary Conditions. Not a single line of extrapolation is used.

What a Boundary Can Count, and What It Must Invent#

Put the boundary at x1=Lx_1 = L. Recast the x1x_1-direction terms as waves and the continuity equation reads:

∂ρ∂t+d1+∂(ρu2)∂x2+∂(ρu3)∂x3=0\frac{\partial \rho}{\partial t} + d_1 + \frac{\partial (\rho u_2)}{\partial x_2} + \frac{\partial (\rho u_3)}{\partial x_3} = 0

Here ρ\rho is density, uiu_i the velocity components, and d1d_1 collects the contributions normal to the boundary. That dd vector is the product of the characteristic analysis, and inside it live the wave amplitudes Li\mathcal{L}_i.

L1=(u1−c)(∂p∂x1−ρc∂u1∂x1)\mathcal{L}_1 = (u_1 - c)\left( \frac{\partial p}{\partial x_1} - \rho c \frac{\partial u_1}{\partial x_1} \right) L5=(u1+c)(∂p∂x1+ρc∂u1∂x1)\mathcal{L}_5 = (u_1 + c)\left( \frac{\partial p}{\partial x_1} + \rho c \frac{\partial u_1}{\partial x_1} \right)

cc is the local speed of sound (c2=γp/ρc^2 = \gamma p / \rho) and pp the pressure. L1\mathcal{L}_1 is the amplitude variation of the acoustic wave running in the negative x1x_1 direction, L5\mathcal{L}_5 the one running positive. The remaining three ride along with the fluid: L2\mathcal{L}_2 carries entropy, L3\mathcal{L}_3 and L4\mathcal{L}_4 carry the transverse velocities u2u_2 and u3u_3, all three moving at u1u_1.

What splits the cases is the sign of those speeds. If a wave leaves the domain, its amplitude is computable from interior points. If it enters, that information is nowhere in the solution. You have to invent it. The number of entering waves is exactly the number of physical boundary conditions the problem allows.

How characteristics split sound across a whole domain is in why a supersonic flow does not know its upstream. Here the count is only the waves cut by one outlet face.

Raise MM from +0.3+0.3 to +1.4+1.4 and the arrow that flipped above is L1\mathcal{L}_1. The condition count goes from one to zero. A negative sign turns the face into an inlet and the count is four. A code that runs at subsonic outflow and diverges once the exit is supersonic is usually imposing one condition more than this ledger allows.

LODI — the Rule That Manufactures an Amplitude#

Inventing an incoming amplitude needs a justification. At each boundary point NSCBC builds a local one-dimensional inviscid system, dropping every transverse, viscous, and reaction term. Those are the LODI relations.

∂p∂t+12(L5+L1)=0\frac{\partial p}{\partial t} + \frac{1}{2}\left( \mathcal{L}_5 + \mathcal{L}_1 \right) = 0 ∂u1∂t+12ρc(L5−L1)=0\frac{\partial u_1}{\partial t} + \frac{1}{2 \rho c}\left( \mathcal{L}_5 - \mathcal{L}_1 \right) = 0

LODI relations are not physical conditions, and they are not the equations you actually solve. They exist only to estimate the incoming Li\mathcal{L}_i. The procedure is three steps: delete each conservation equation whose variable is physically imposed, use the matching LODI relation to write the unknown Li\mathcal{L}_i in terms of the known ones, then advance the remaining equations for everything else.

A perfectly non-reflecting outlet makes the simplest possible choice here — it declares that no acoustic wave arrives from outside.

L1=0\mathcal{L}_1 = 0

Zero incoming wave, zero reflection. It looks clean. But notice that the outer pressure p∞p_\infty appears nowhere in that statement.

What σ = 0 Costs: Pressure That Never Returns to p∞#

No p∞p_\infty means the boundary has no idea what the pressure is supposed to be. Heat released inside the domain raises the pressure, and nothing anywhere pulls that offset back. The problem stops being well posed.

The fix from Rudy and Strikwerda is to stop setting the incoming wave to zero and instead tie it to the pressure difference:

L1=K(p−p∞),K=σ(1−M2)cL\mathcal{L}_1 = K \left( p - p_\infty \right), \qquad K = \sigma \left( 1 - M^2 \right) \frac{c}{L}

LL is a characteristic domain length, MM the maximum Mach number, and σ\sigma is the only free parameter in the whole prescription. At σ=0\sigma = 0 you are back to perfectly non-reflecting. Raise σ\sigma and the boundary starts dragging the pressure toward p∞p_\infty.

The closed duct, with sigma and source q, sits immediately after the printed table.

The Narrow Window Between Two Failures#

σ\sigma sits between two failures that pull in opposite directions.

TreatmentWhat the outlet doesHow it fails
B1 (extrapolation + Riemann invariants)Extrapolate velocity and density, relax pressure onlySpurious waves manufactured by extrapolation
B2 (NSCBC, σ=0\sigma = 0)L1=0\mathcal{L}_1 = 0Mean pressure never anchored to p∞p_\infty
B3 (NSCBC, σ>0\sigma > 0)L1=K(p−p∞)\mathcal{L}_1 = K(p - p_\infty)Reflection once σ\sigma is large
B4 (reflecting outlet)Pressure fixed, L1=L5\mathcal{L}_1 = \mathcal{L}_5Total reflection — the box is a resonator

Small σ\sigma lets the mean pressure drift; large σ\sigma stiffens the boundary until it throws acoustic energy back inside. The values Poinsot and Lele actually used were σ≈0.25\sigma \approx 0.25, and σ=0.58\sigma = 0.58 for the equivalent coefficient in the extrapolation-based B1. Neither number comes from theory. Both were picked from between the two failures.

The frequency dependence shows why it is a compromise. For an acoustic wave of angular frequency ω\omega, this boundary reflects with

∣R∣=KK2+4ω2|R| = \frac{K}{\sqrt{K^2 + 4\omega^2}}

Lower frequencies reflect more. So σ\sigma is really a filter that grabs low frequencies and lets high ones through. Mean pressure is the ω→0\omega \to 0 component, so it gets grabbed; the acoustics you want to evacuate pass. The window where that separation works is narrow.

Measuring Reflection and Offset in Code#

For one-dimensional linear acoustics the state splits exactly into two characteristic amplitudes: A±=p′±ρcu′A^\pm = p' \pm \rho c u', traveling at ±c\pm c. Choose Δt=Δx/c\Delta t = \Delta x / c and each one shifts exactly one cell per step, so every wiggle on screen comes from the boundary condition rather than the scheme.

import numpy as np
 
N, C, L = 240, 1.0, 1.0
DX = L / N
DT = DX / C                       # exact one-cell shift: no scheme diffusion
 
 
def outlet_relax_k(sigma, mach=0.0):
    """NSCBC relaxation coefficient K = sigma (1 - M^2) c / L"""
    return sigma * (1.0 - mach ** 2) * C / L
 
 
def duct_step(ap, am, am_b, k_relax, q):
    """A+ one cell right, A- one cell left. At the outlet A- must be invented."""
    ap[1:] = ap[:-1].copy()
    ap[0] = 0.0
    am[:-1] = am[1:].copy()
 
    p_b = 0.5 * (ap[-1] + am[-1])
    am_b -= DT * k_relax * p_b     # L1 = K (p - p_inf)
    am[-1] = am_b
 
    ap[0] = am[0]                  # closed end at x = 0 (u = 0)
    ap += q * DT                   # weak uniform heat release
    am += q * DT
    return am_b
 
 
def measure_outlet(sigma, q, steps, pulse):
    ap, am, am_b = np.zeros(N), np.zeros(N), 0.0
    x = (np.arange(N) + 0.5) * DX
    if pulse:
        ap += np.exp(-((x - 0.30) / 0.09) ** 2)
    k = outlet_relax_k(sigma)
    refl = 0.0
    for n in range(steps):
        am_b = duct_step(ap, am, am_b, k, q)
        if pulse and n > 0.85 * N:
            refl = max(refl, np.abs(am[:-8]).max())
    return refl, float(np.mean(0.5 * (ap + am)))
 
 
for sigma in (0.0, 0.25, 1.0, 4.0, 10.0):
    r, _ = measure_outlet(sigma, q=0.0, steps=650, pulse=True)
    _, p = measure_outlet(sigma, q=0.3, steps=6000, pulse=False)
    print(f"sigma={sigma:5.2f}   reflected={r * 100:5.1f}%   mean p - p_inf={p:+.4f}")

What it prints:

sigma= 0.00   reflected=  0.0%   mean p - p_inf=+0.3000
sigma= 0.25   reflected=  1.9%   mean p - p_inf=+0.0007
sigma= 1.00   reflected=  7.3%   mean p - p_inf=+0.0006
sigma= 4.00   reflected= 24.5%   mean p - p_inf=+0.0227
sigma=10.00   reflected= 46.8%   mean p - p_inf=-0.1142
fire a pulse and watch the red curve at the outlet — that is the reflection. then drop sigma to 0 and watch the white curve settle above p_inf instead of returning to it.

When a fire pulse hits the outlet, a rising red curve A−A^- is the reflection. With source q up and σ=0\sigma = 0, the white curve sits above p∞p_\infty. The reflection is gone. The pressure is not.

At σ=0\sigma = 0 the reflection is gone completely, and the 0.3 offset built by the heat release sits there untouched. At σ=0.25\sigma = 0.25 reflection is under 2% and the pressure is held within 0.0007 of p∞p_\infty. So far, as expected.

The surprise is the last two rows. Pushing σ\sigma to 4 and 10 raises reflection to 25% and 47%, which is unsurprising — but the pressure offset gets worse again. A boundary that stiff starts ringing on its own, and the ringing moves the mean. Raising σ\sigma is not a trade where you buy pressure control with reflection. Past a certain point you lose both.

The reflection column reads 0.0, 1.9, 7.3, 24.5, 46.8%. In the code C=1C = 1, L=1L = 1, and Mach is 0, so K=σ(1−M2)cLK = \sigma \left( 1 - M^2 \right) \frac{c}{L} collapses to K=σK = \sigma. Raising σ\sigma from 0.25 to 10, a factor of 40, moves the reflection from 1.9 to 46.8. About 25 times. Not linear. ∣R∣=KK2+4ω2|R| = \frac{K}{\sqrt{K^2 + 4\omega^2}} saturates toward 1 as K grows. The pulse is not a single frequency. It is a Gaussian of width 0.09. Do not invert one ω\omega out of these percentages. The direction is enough. Larger K reflects more, then the climb flattens.

The pressure column does not follow that monotone. In the run with q=0.3q = 0.3, σ=0\sigma = 0 leaves a mean of +0.3000, the same size as the source. σ=0.25\sigma = 0.25 leaves +0.0007 and σ=1\sigma = 1 leaves +0.0006. Against 0.3000, 0.0007 is 0.23%. Up to there the relaxation nearly erases the offset. At σ=4\sigma = 4 the offset is +0.0227, about 32 times 0.0007. At σ=10\sigma = 10 it is −0.1142. The sign flips. The absolute value is about 160 times 0.0007. A boundary reflecting 46.8% does not hold the mean. It shakes it. The floor of the offset column sits at 0.25 and 1.0. The 0.25 Poinsot and Lele used is the left edge of that floor. 0.58 belongs to the B1 coefficient, so it does not land on this column as printed.

Waves the Grid Invented Travel the Wrong Way#

Physical acoustics are not the only thing reflecting off the boundary. Components with wavelengths shorter than about four mesh spacings are not solutions of Navier–Stokes at all; they are artifacts of the discretization. Poinsot and Lele called these "q waves" to separate them from the physical "p waves."

Their signature is group velocity. Even for the one-dimensional advection equation at speed VV, the group velocity ugu_g of short wavelengths has the opposite sign to VV. The flow moves right, and the numerical error crawls upstream to the left. Worse, ∣ug/V∣|u_g / V| grows with the order of the spatial scheme, so high-order codes are more exposed, not less.

That is why a boundary treatment has to be judged with two reflection coefficients: Ap/A1A_p / A_1 for physical waves and Aq/A1A_q / A_1 for numerical ones. Any usable treatment needs Aq/A1≪1A_q / A_1 \ll 1 under all circumstances; one that claims to be non-reflecting also needs Ap/A1≪1A_p / A_1 \ll 1. Simply starting a run from an initial field with steep gradients generates q waves, and in a DNS nothing removes them afterward.

If the Period Is 2L/c, Suspect Outlet σ First#

σ\sigma is not a tuning knob. One end is a pressure tied to nothing. The other end is a box that has become a resonator. Near 0.25 the boundary holds the low frequencies and lets the rest through.

An oscillation with no visible cause starts with a count: incoming characteristics versus imposed conditions. A period that is a multiple of 2L/c2L/c is the boundary, not the physics. Lower σ\sigma. If the oscillation shrinks, the boundary was the cause. If the mean pressure drifts, the run has crossed to the other side. Counting those waves before touching the outlet is the point of an earlier pass that sets NSCBC up as a non-reflecting outflow.

Share if you found it helpful.