18) Hyperbolic Systems#
Last time:
Godunov’s Theorem
Slope reconstruction and limiting
Common limiters in the FV literature
Introduction on hyperbolic systems
Example: Isentropic gas dynamics
Today:
Hyperbolic systems
1.1 Riemann problem for systems: shocks
1.2 Admissible shocksGeneralized Riemann invariants
Exact Riemann solver for isothermal gas dynamics
Approximate Riemann solvers
4.1 HLL (Harten, Lax, and van Leer)
using LinearAlgebra
using Plots
default(linewidth=3)
# utility functions seen in class
struct RKTable
A::Matrix
b::Vector
c::Vector
function RKTable(A, b)
s = length(b)
A = reshape(A, s, s)
c = vec(sum(A, dims=2))
new(A, b, c)
end
end
rk4 = RKTable([0 0 0 0; .5 0 0 0; 0 .5 0 0; 0 0 1 0], [1, 2, 2, 1] / 6)
# explicit RK solver
function ode_rk_explicit(f, u0; tfinal=1., h=0.1, table=rk4)
u = copy(u0)
t = 0.
n, s = length(u), length(table.c)
fY = zeros(n, s)
thist = [t]
uhist = [u0]
while t < tfinal
tnext = min(t+h, tfinal)
h = tnext - t
for i in 1:s
ti = t + h * table.c[i]
Yi = u + h * sum(fY[:,1:i-1] * table.A[i,1:i-1], dims=2)
fY[:,i] = f(ti, Yi)
end
u += h * fY * table.b
t = tnext
push!(thist, t)
push!(uhist, u)
end
thist, hcat(uhist...)
end
function testfunc(x)
max(1 - 4*abs.(x+2/3),
abs.(x) .< .2,
(2*abs.(x-2/3) .< .5) * cospi(2*(x-2/3)).^2
)
end
flux_advection(u) = u
flux_burgers(u) = u^2/2
flux_traffic(u) = u * (1 - u)
riemann_advection(uL, uR) = 1*uL # velocity is +1
function fv_solve1(riemann, u_init, n, tfinal=1)
h = 2 / n # 1D domain [-1,1]
x = LinRange(-1+h/2, 1-h/2, n) # cell midpoints (centroids)
idxL = 1 .+ (n-1:2*n-2) .% n # left interface indices
idxR = 1 .+ (n+1:2*n) .% n # right interface indices
function rhs(t, u)
fluxL = riemann(u[idxL], u)
fluxR = riemann(u, u[idxR])
(fluxL - fluxR) / h
end
thist, uhist = ode_rk_explicit(rhs, u_init.(x), h=h, tfinal=tfinal)
x, thist, uhist
end
function riemann_burgers(uL, uR)
flux = zero(uL)
for i in 1:length(flux)
fL = flux_burgers(uL[i])
fR = flux_burgers(uR[i])
flux[i] = if uL[i] > uR[i] # shock
max(fL, fR)
elseif uL[i] > 0 # rarefaction all to the right
fL
elseif uR[i] < 0 # rarefaction all to the left
fR
else
0
end
end
flux
end
limit_zero(r) = 0
limit_none(r) = 1
limit_minmod(r) = max(min(2*r, 2*(1-r)), 0)
limit_sin(r) = (0 < r && r < 1) * sinpi(r)
limit_vl(r) = max(4*r*(1-r), 0)
limit_bj(r) = max(0, min(1, 4*r, 4*(1-r)))
limiters = [limit_zero limit_none limit_minmod limit_sin limit_vl limit_bj];
1. Hyperbolic systems#
Systems of the form
It would be too good if we could safely reconstruct each component of the flux in the same way.
But the system has waves traveling at different speeds. So we need to be more careful with reconstructions.
As in the scalar case, elementary waves may propagate as shock waves, or as rarefaction fans, except now they may occur in each of the different characteristic fields.
Isentropic gas dynamics#
Variable |
meaning |
|---|---|
\(\rho\) |
density |
\(u\) |
velocity |
\(\rho u\) |
momentum |
\(p\) |
pressure |
Isentrtropic equation of state (pressure depends only on density) \(p(\rho) = C \rho^\gamma\) with \(\gamma = 1.4\) (typical air).
“isothermal” gas dynamics: \(p(\rho) = c^2 \rho\), wave speed \(c\).
Compute the needed \(\rho u^2\) as \( \rho u^2 = \frac{(\rho u)^2}{\rho} .\)
The flux Jacobian is
Smooth wave structure#
For perturbations of a constant state, systems of equations produce multiple waves with speeds equal to the eigenvalues of the flux Jacobian,
1.1 Riemann problem for systems: shocks#
Given states \(U_L\) and \(U_R\), we will see two waves with some state \(U^*\) in between.
There could be two shocks, two rarefactions, or one of each.
The type of wave will determine the condition that must be satisfied to connect \(U_L\) to \(U_*\) and \(U_*\) to \(U_R\).
Left-moving wave#
If there is a shock between \(U_L\) and \(U_*\), the Rankine-Hugoniot condition
will be satisfied along with the entropy condition
where \(\lambda_i(U)\) are the corresponding eigenvalues of the flux Jacobian \(F'(U)\). These are the characteristic speeds.
This condition states:
The shock speed lies between the characteristic speeds on each side.
The inequality in the entropy condition is strict if the wave is genuinely nonlinear (more on this in a second). Meaning, \( \lambda_i(U_L) \ge S \ge \lambda_i(U_*) \).
For isentropic/isothermal gas dynamics,
If a shock connects \(U_L = (\rho_L, \rho_L u_L)\) to \(U_* = (\rho_* , \rho_* u_*)\), with shock speed \(S\), and
in this case we have
the Rankine-Hugoniot condition is, component-wise:
Nonlinear Systems#
For a system
We have again, the Jacobian
\(\lambda_i(U)\) are the eigenvalues (wave speeds)
\(w_i(U)\) are the corresponding eigenvectors
A field (wave family) \(i\) is called genuinely nonlinear if
and we say that the \(i\)-th characteristic field is linearly degenerate if
Observations#
These conditions generalize the scalar case \(u_t + f(u)_x = 0 \).
The genuinely nonlinear case, in the scalar case, reduces to the scalar convexity condition.
A genuinely nonlinear field admits elementary waves that are either shock waves (converging characteristics) or expansion fans (diverging characteristics)
While a linearly degenerate field admits simple waves called contact discontinuities, across which the characteristics remain parallel as in the linear case. In fact, in this case wave speed stays constant along the wave - no steepening occurs.
Again, for the genuinely nonlinear case, the entropy condition is strict \( \lambda_i(U_L) \ge S \ge \lambda_i(U_*) \).
Rankine–Hugoniot for the isentropic gas dynamics example#
Going back to the isentropic/isothermal gas dynamics example, we solve the first equation (i) for \(S\) and substituting into the second (ii):
By doing so, we have found the Hugoniot locus, the algebraic relation that admissible shock-connected states \(U_*\) must satisfy.
Note that algebraically, there are two possible solutions (\(\pm\) sign) and we will need to use the entropy condition to learn which sign to take. One corresponds to a physical shock, the other corresponds to a nonphysical solution (e.g. expansion shock). The Rankine–Hugoniot condition(s) alone does not distinguish them. That’s why we also need the entropy condition.
Admissible shocks#
We need to choose the sign
The entropy condition requires that
Right-moving wave#
The same process for the right wave with characteristic speed \(\lambda(U) = u + c\) yields a shock when \(\rho_* \ge \rho_R\), in which case the velocity jump is
Rarefactions#
A rarefaction occurs when the entropy condition is not satisfied, that is
2. Generalized Riemann invariants#
For a system, with eigenvalues \(\lambda_i(U)\) and corresponding eigenvectors \(w_i(U)\), a Riemann invariant for the \(i\)-th field is a scalar function \(r(U)\) such that
(Derivation of this condition is beyond the scope of this class.)
This is a first-order linear PDE, whose solution gives invariants. \(r\) does not change along the \(i\)-th characteristic direction (hence, the invariant name).
Why are they useful?
They let you reduce a system to something like a scalar problem along each wave family.
In particular:
Across a rarefaction wave, Riemann invariants are constant
They give you explicit relations between states
We will use:
Isentropic/isothermal gas dynamics#
across the wave \(\lambda = u-c\). We can rearrange to
Integration yields
First a miracle happens: in this case it is exactly integrable
In general we will need to use a Newton method to solve for the state in the star region.
Basic algorithm#
Find \(\rho_*\)
Use entropy condition (for shock speeds)
If it’s a shock: find \(u_*\) using Rankine-Hugoniot
If it’s a rarefaction: use generalized Riemann invariant
3. Exact Riemann solver for isothermal gas dynamics#
function flux_isogas(U, c=1)
rho = U[1]
u = U[2] / rho
[U[2], U[2]*u + c^2*rho]
end
function ujump_isogas(rho_L, rho_R, cw)
if sign(rho_L - rho_R) == sign(cw) # shock
sign(cw) * (rho_R - rho_L) / sqrt(rho_L*rho_R)
else # rarefaction
cw * (log(rho_R) - log(rho_L))
end
end
function dujump_isogas(rho_L, drho_L, rho_R, drho_R, cw)
if sign(rho_L - rho_R) == sign(cw) # shock
sign(cw) * ((drho_R - drho_L) / sqrt(rho_L*rho_R)
- .5*(rho_R - rho_L) * (rho_L*rho_R)^(-3/2)
* (drho_L * rho_R + rho_L * drho_R))
else
cw * (drho_R / rho_R - drho_L / rho_L)
end
end
dujump_isogas (generic function with 1 method)
function riemann_isogas(UL, UR, maxit=20; show=false)
rho_L, u_L = UL[1], UL[2]/UL[1]
rho_R, u_R = UR[1], UR[2]/UR[1]
rho = .5 * (rho_L + rho_R) # initial guess
U_star = zero.(UL)
for i in 1:maxit
f = (ujump_isogas(UL[1], rho, -1)
+ ujump_isogas(rho, UR[1], 1)
- (UR[2]/UR[1] - UL[2]/UL[1]))
if norm(f) < 1e-10
u = u_L + ujump_isogas(UL[1], rho, -1)
U_star[:] = [rho, rho*u]
break
end
J = (dujump_isogas(rho_L, 0, rho, 1, -1)
+ dujump_isogas(rho, 1, rho_R, 0, 1))
delta_rho = -f / J
rho += delta_rho
## Line search not needed in practice
#while rho + delta_rho <= 0
# delta_rho /= 2 # line search to prevent negative rho
#end
end
U0 = resolve_isogas(UL, UR, U_star)
if show; @show U0, U0[2]/U0[1]; end
flux_isogas(U0)
end
riemann_isogas (generic function with 2 methods)
Resolving waves for isothermal gas dynamics#
function resolve_isogas(UL, UR, U_star; c=1)
rho_L, u_L = UL[1], UL[2]/UL[1]
rho_R, u_R = UR[1], UR[2]/UR[1]
rho, u = U_star[1], U_star[2] / U_star[1]
if ((u_L - c < 0 < u - c) ||
(u + c < 0 < u_R + c))
# inside left (right) sonic rarefaction
u0 = sign(u) * c
rho0 = exp((u0-u)/c + log(rho))
@show "sonic"
[rho0, rho0 * u0]
elseif ((rho_L >= rho && 0 <= u_L - c) ||
(rho_L < rho && 0 < rho*u - UL[2]))
# left rarefaction or shock is supersonic
UL
elseif ((rho_R >= rho && u_R + c <= 0) ||
(rho_R < rho && rho*u - UR[2] < 0))
# right rarefaction or shock is supersonic
UR
else # sample star region
U_star
end
end
resolve_isogas (generic function with 1 method)
Test the Riemann solver#
isogas(rho, u) = [rho, rho*u]
riemann_isogas(isogas(1, .1), isogas(.9, .1), show=true)
(U0, U0[2] / U0[1]) = ([0.9486804089487428, 0.14484765853290457], 0.15268330321421308)
2-element Vector{Float64}:
0.14484765853290457
0.9707962279163911
function initial_isogas(x)
f(s) = isogas(1 + 2 * exp(-4s ^ 2), 0.1)
vcat(f.(x)'...)
end
x = LinRange(-1, 1, 100)
U = initial_isogas(x)
plot(x, [U (U[:, 2] ./ U[:, 1])],
label=["density" "momentum" "velocity"])
Solver#
function fv_solve2system(riemann, initfunc, n, tfinal=1; dt_scale=1, limit=limit_sin)
h = 2 / n
x = LinRange(-1+h/2, 1-h/2, n) # cell midpoints (centroids)
idxL = 1 .+ (n-1:2*n-2) .% n
idxR = 1 .+ (n+1:2*n) .% n
U0 = initfunc(x)
n, k = size(U0)
function rhs(t, U)
U = reshape(U, n, k)
jump = U[idxR, :] - U[idxL, :]
r = (U - U[idxL, :]) ./ jump
r[.~isfinite.(r)] .= 0
g = limit.(r) .* jump / 2h
flux = zero.(U)
for i in 1:n
UL = U[idxL[i],:] + g[idxL[i],:] * h/2
UR = U[i,:] - g[i,:] * h/2
flux[i, :] = riemann(UL, UR)
end
vec(flux - flux[idxR, :]) / h
end
thist, uhist = ode_rk_explicit(
rhs, vec(U0), h=h*dt_scale, tfinal=tfinal)
x, thist, reshape(uhist, n, k, length(thist))
end
fv_solve2system (generic function with 2 methods)
x, t_hist, U_hist = fv_solve2system(riemann_isogas,
initial_isogas, 200, 1.5, dt_scale=0.8)
rho_hist = U_hist[:, 1, :]
u_hist = U_hist[:, 2, :] ./ rho_hist
plot(x, rho_hist[:, 1:20:end],
label=round.(t_hist[1:20:end]', digits=2)) # check both components
We can see the initial smooth solutions develop shock and rarefaction structure.
Visualize solutions#
function initial_isogas_2(x)
f(s) = isogas(1 + 2 * (abs(s) < 0.25), 0.0)
vcat(f.(x)'...)
end
x, t_hist, U_hist = fv_solve2system(riemann_isogas,
initial_isogas_2, 200, 1, dt_scale=0.5, limit=limit_sin)
step = length(t_hist) ÷ 5; rho_hist = U_hist[:, 1, :]
plot(x, rho_hist[:, 1:step:end], label=round.(t_hist[1:step:end]', digits=2), title="density")
u_hist = U_hist[:, 2, :] ./ rho_hist
plot(x, u_hist[:, 1:step:end], label=round.(t_hist[1:step:end]', digits=2), title="velocity")
4. Approximate Riemann solvers#
Exact Riemann solvers are
complicated to implement
fragile in the sense that small changes to the physics, such as in the equation of state \(p(\rho)\), can require changing many conditionals
the need to solve for \(\rho^*\) using a Newton method and then evaluate each case of the wave structure can be expensive.
An exact Riemann solver has never been implemented for some equations.
4.1 HLL (Harten, Lax, and van Leer)#
Assume two shocks with speeds \(S_L\) and \(S_R\).
These speeds will be estimated and must be at least as fast as the fastest left- and right-traveling waves.
If the wave speeds \(S_L\) and \(S_R\) are given, we have the Rankine-Hugoniot conditions across both shocks,
Adding these together gives
Isentropic/isothermal gas dynamics example#
Rusanov method#
Special case \(S_L = - S_R\), in which case the wave structure is always subsonic and the flux is simply
Observations on HLL solvers#
The term involving \(U_R-U_L\) represents diffusion and will cause entropy to decay (physical entropy is produced).
If our Riemann problem produces shocks and we have correctly calculated the wave speeds, the HLL solver is exact and produces the minimum diffusion necessary for conservation.
If the wave speed estimates are slower than reality, the method will be unstable due to CFL.
If the wave speed estimates are faster than reality, the method will be more diffusive than an exact Riemann solver.