Brio-Wu MHD Shock Tube Verification
This example performs a grid convergence study of the Brio-Wu MHD shock tube (Brio & Wu, 1988) and verifies key solution properties against the published reference.
Mathematical Setup
The Brio-Wu problem is the MHD analogue of the Sod shock tube with $\gamma = 2$ and a constant normal magnetic field $B_x = 0.75$:
\[(\rho,v_x,v_y,v_z,P,B_x,B_y,B_z) = \begin{cases} (1, 0, 0, 0, 1, 0.75, 1, 0) & x < 0.5 \\ (0.125, 0, 0, 0, 0.1, 0.75, -1, 0) & x \ge 0.5 \end{cases}\]
The sign change in $B_y$ produces compound MHD waves.
References
- Brio, M. & Wu, C.C. (1988). An Upwind Differencing Scheme for the Equations of Ideal Magnetohydrodynamics. J. Comput. Phys., 75, 400-422.
- Balsara, D.S. (2001). Total Variation Diminishing Scheme for Adiabatic and Isothermal MHD. J. Comput. Phys., 174, 614-648.
using FiniteVolumeMethod
using OrdinaryDiffEq
using SciMLBase: ReturnCode
using StaticArrays
using CairoMakie
gamma = 2.0
eos = IdealGasEOS(gamma)
law = IdealMHDEquations{1}(eos)
Bx = 0.75
wL = SVector(1.0, 0.0, 0.0, 0.0, 1.0, Bx, 1.0, 0.0)
wR = SVector(0.125, 0.0, 0.0, 0.0, 0.1, Bx, -1.0, 0.0)
bw_ic(x) = x < 0.5 ? wL : wR
t_final = 0.10.1Grid Convergence Study
We use a very fine grid (N=1600) as the reference solution and compute L1 errors of coarser grids against it.
function solve_bw(N)
mesh = StructuredMesh1D(0.0, 1.0, N)
prob = HyperbolicProblem(
law, mesh, HLLDSolver(), CellCenteredMUSCL(MinmodLimiter()),
TransmissiveBC(), TransmissiveBC(), bw_ic;
final_time = t_final, cfl = 0.5,
)
ode_prob = sciml_problem(prob)
dt0 = compute_initial_dt(ode_prob.p, ode_prob.u0)
sol = solve(prob, SSPRK33(); adaptive = false, dt = dt0)
accessor = solution_accessor(prob)
x = solution_coordinates(accessor)
W = get_primitive(accessor, sol, length(sol.t))
rho = [W[i][1] for i in eachindex(W)]
return x, W, rho, sol.t[end]
endsolve_bw (generic function with 1 method)Reference solution at N=1600
x_ref, _, rho_ref, _ = solve_bw(1600)([0.0003125, 0.0009375, 0.0015625, 0.0021875, 0.0028125, 0.0034375, 0.0040625, 0.0046875, 0.0053125, 0.0059375 … 0.9940625000000001, 0.9946875000000001, 0.9953125, 0.9959375, 0.9965625, 0.9971875, 0.9978125, 0.9984375, 0.9990625000000001, 0.9996875000000001], StaticArraysCore.SVector{8, Float64}[[1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.75, 1.0, 0.0] … [0.12500000000000092, 1.3276895326913305e-15, -3.3297877224593715e-16, 0.0, 0.09999999999999931, 0.75, -1.0000000000000002, 0.0], [0.1250000000000048, 3.673751020564344e-17, 1.6928603867924768e-15, 0.0, 0.09999999999999876, 0.75, -1.0000000000000004, 0.0], [0.12500000000000008, -1.897330020861553e-15, 4.247002979920196e-15, 0.0, 0.09999999999999898, 0.75, -1.0000000000000007, 0.0], [0.12500000000000008, -2.2811760234305676e-15, 5.191694229511292e-15, 0.0, 0.09999999999999931, 0.75, -1.0000000000000002, 0.0], [0.12500000000000006, -2.1006730328146117e-15, 5.78839669720103e-15, 0.0, 0.09999999999999942, 0.75, -1.0000000000000002, 0.0], [0.12500000000000008, -1.9947228387162366e-15, 5.040386818738677e-15, 0.0, 0.09999999999999942, 0.75, -1.0000000000000002, 0.0], [0.12500000000000006, -1.9741751961847253e-15, 5.576488118787983e-15, 0.0, 0.09999999999999942, 0.75, -1.0000000000000002, 0.0], [0.12500000000000008, -1.868235504405975e-15, 5.2047907807448036e-15, 0.0, 0.09999999999999942, 0.75, -1.0000000000000002, 0.0], [0.12499999999999979, -1.577402903164572e-15, 4.904104774283873e-15, 0.0, 0.09999999999999942, 0.75, -1.0000000000000002, 0.0], [0.125, -1.4303471194779435e-15, 4.164595147719854e-15, 0.0, 0.09999999999999942, 0.75, -1.0000000000000002, 0.0]], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 … 0.12500000000000092, 0.1250000000000048, 0.12500000000000008, 0.12500000000000008, 0.12500000000000006, 0.12500000000000008, 0.12500000000000006, 0.12500000000000008, 0.12499999999999979, 0.125], 0.1)Interpolate reference to coarse grid via nearest-neighbour
function interp_nearest(x_coarse, x_fine, vals_fine)
return [vals_fine[argmin(abs.(x_fine .- xc))] for xc in x_coarse]
end
grid_sizes = [100, 200, 400, 800]
errors_rho = Float64[]
for N in grid_sizes
x_c, _, rho_c, _ = solve_bw(N)
rho_ref_interp = interp_nearest(x_c, x_ref, rho_ref)
err = sum(abs(rho_c[i] - rho_ref_interp[i]) for i in eachindex(rho_c)) / N
push!(errors_rho, err)
endConvergence Rates
function convergence_rates(errs)
return [log2(errs[i] / errs[i + 1]) for i in 1:(length(errs) - 1)]
end
rates = convergence_rates(errors_rho)3-element Vector{Float64}:
0.8976532813700772
1.0241438741437436
1.4745761759977127Visualisation — Solutions at Multiple Resolutions
fig1 = Figure(fontsize = 20, size = (1400, 450))
for (panel, N_idx) in enumerate([1, 2, 4])
local Nv = grid_sizes[N_idx]
x_c, _, rho_c, _ = solve_bw(Nv)
ax = Axis(
fig1[1, panel], xlabel = "x", ylabel = L"\rho",
title = "N = $(Nv)"
)
lines!(ax, x_ref, rho_ref, color = :black, linewidth = 1.5, label = "Ref (1600)")
scatter!(ax, x_c, rho_c, color = :blue, markersize = 3, label = "HLLD+MUSCL")
panel == 1 && axislegend(ax, position = :cb)
end
resize_to_layout!(fig1)
fig1
if isdefined(@__MODULE__, :evidence_artifact_path)
save(evidence_artifact_path("brio_wu_verification_solutions.png"), fig1)
endVisualisation — Convergence Plot
fig2 = Figure(fontsize = 24, size = (700, 550))
ax = Axis(
fig2[1, 1], xlabel = "N", ylabel = L"L^1 \text{ error } (\rho)",
xscale = log2, yscale = log10,
title = "Brio-Wu Grid Convergence",
)
scatterlines!(
ax, grid_sizes, errors_rho, color = :blue, marker = :circle,
linewidth = 2, markersize = 12, label = "HLLD+MUSCL"
)
e_ref = errors_rho[1]
N_ref = grid_sizes[1]
lines!(
ax, grid_sizes, e_ref .* (N_ref ./ grid_sizes) .^ 1,
color = :black, linestyle = :dash, linewidth = 1, label = L"O(N^{-1})",
)
axislegend(ax, position = :lb)
resize_to_layout!(fig2)
fig2
if isdefined(@__MODULE__, :evidence_artifact_path)
save(evidence_artifact_path("brio_wu_verification_convergence.png"), fig2)
endStructural Verification
Check that the high-resolution solution has expected physical properties.
_, W_hr, rho_hr, _ = solve_bw(800)
Bx_vals = [W_hr[i][6] for i in eachindex(W_hr)]
By_vals = [W_hr[i][7] for i in eachindex(W_hr)]800-element Vector{Float64}:
1.0
1.0
1.0
1.0
1.0
1.0
1.0
1.0
1.0
1.0
⋮
-1.0000000000000002
-1.0000000000000002
-1.0000000000000002
-1.0000000000000002
-1.0000000000000002
-1.0000000000000002
-1.0000000000000002
-1.0000000000000002
-0.9999999999999999Test Assertions
- Monotone error convergence
- Bx remains constant at 0.75 (normal B is conserved in 1D MHD)
- By changes sign across the contact (key Brio-Wu feature)
- Density stays positive
if isdefined(@__MODULE__, :record_evidence_result)
record_evidence_result(
metrics = Dict(
"density_errors" => errors_rho,
"density_rates" => rates,
"min_density_rate" => minimum(rates),
"finest_density_error" => errors_rho[end],
"by_min" => minimum(By_vals),
"by_max" => maximum(By_vals),
),
artifacts = [
"brio_wu_verification_solutions.png",
"brio_wu_verification_convergence.png",
],
notes = [
"Canonical SciML execution path via sciml_problem(prob) and solve(prob, SSPRK33()).",
"This evidence entry is the mhd_ct benchmark-stage Brio-Wu reference comparison case.",
],
summary = Dict(
"grid_sizes" => grid_sizes,
"density_errors" => errors_rho,
"density_rates" => rates,
"bx_constant" => all(b -> abs(b - Bx) < 1.0e-10, Bx_vals),
"density_positive" => all(rho_hr .> 0),
),
)
endJust the code
An uncommented version of this example is given below. You can view the source code for this file here.
using FiniteVolumeMethod
using OrdinaryDiffEq
using SciMLBase: ReturnCode
using StaticArrays
using CairoMakie
gamma = 2.0
eos = IdealGasEOS(gamma)
law = IdealMHDEquations{1}(eos)
Bx = 0.75
wL = SVector(1.0, 0.0, 0.0, 0.0, 1.0, Bx, 1.0, 0.0)
wR = SVector(0.125, 0.0, 0.0, 0.0, 0.1, Bx, -1.0, 0.0)
bw_ic(x) = x < 0.5 ? wL : wR
t_final = 0.1
function solve_bw(N)
mesh = StructuredMesh1D(0.0, 1.0, N)
prob = HyperbolicProblem(
law, mesh, HLLDSolver(), CellCenteredMUSCL(MinmodLimiter()),
TransmissiveBC(), TransmissiveBC(), bw_ic;
final_time = t_final, cfl = 0.5,
)
ode_prob = sciml_problem(prob)
dt0 = compute_initial_dt(ode_prob.p, ode_prob.u0)
sol = solve(prob, SSPRK33(); adaptive = false, dt = dt0)
accessor = solution_accessor(prob)
x = solution_coordinates(accessor)
W = get_primitive(accessor, sol, length(sol.t))
rho = [W[i][1] for i in eachindex(W)]
return x, W, rho, sol.t[end]
end
x_ref, _, rho_ref, _ = solve_bw(1600)
function interp_nearest(x_coarse, x_fine, vals_fine)
return [vals_fine[argmin(abs.(x_fine .- xc))] for xc in x_coarse]
end
grid_sizes = [100, 200, 400, 800]
errors_rho = Float64[]
for N in grid_sizes
x_c, _, rho_c, _ = solve_bw(N)
rho_ref_interp = interp_nearest(x_c, x_ref, rho_ref)
err = sum(abs(rho_c[i] - rho_ref_interp[i]) for i in eachindex(rho_c)) / N
push!(errors_rho, err)
end
function convergence_rates(errs)
return [log2(errs[i] / errs[i + 1]) for i in 1:(length(errs) - 1)]
end
rates = convergence_rates(errors_rho)
fig1 = Figure(fontsize = 20, size = (1400, 450))
for (panel, N_idx) in enumerate([1, 2, 4])
local Nv = grid_sizes[N_idx]
x_c, _, rho_c, _ = solve_bw(Nv)
ax = Axis(
fig1[1, panel], xlabel = "x", ylabel = L"\rho",
title = "N = $(Nv)"
)
lines!(ax, x_ref, rho_ref, color = :black, linewidth = 1.5, label = "Ref (1600)")
scatter!(ax, x_c, rho_c, color = :blue, markersize = 3, label = "HLLD+MUSCL")
panel == 1 && axislegend(ax, position = :cb)
end
resize_to_layout!(fig1)
fig1
if isdefined(@__MODULE__, :evidence_artifact_path)
save(evidence_artifact_path("brio_wu_verification_solutions.png"), fig1)
end
fig2 = Figure(fontsize = 24, size = (700, 550))
ax = Axis(
fig2[1, 1], xlabel = "N", ylabel = L"L^1 \text{ error } (\rho)",
xscale = log2, yscale = log10,
title = "Brio-Wu Grid Convergence",
)
scatterlines!(
ax, grid_sizes, errors_rho, color = :blue, marker = :circle,
linewidth = 2, markersize = 12, label = "HLLD+MUSCL"
)
e_ref = errors_rho[1]
N_ref = grid_sizes[1]
lines!(
ax, grid_sizes, e_ref .* (N_ref ./ grid_sizes) .^ 1,
color = :black, linestyle = :dash, linewidth = 1, label = L"O(N^{-1})",
)
axislegend(ax, position = :lb)
resize_to_layout!(fig2)
fig2
if isdefined(@__MODULE__, :evidence_artifact_path)
save(evidence_artifact_path("brio_wu_verification_convergence.png"), fig2)
end
_, W_hr, rho_hr, _ = solve_bw(800)
Bx_vals = [W_hr[i][6] for i in eachindex(W_hr)]
By_vals = [W_hr[i][7] for i in eachindex(W_hr)]
if isdefined(@__MODULE__, :record_evidence_result)
record_evidence_result(
metrics = Dict(
"density_errors" => errors_rho,
"density_rates" => rates,
"min_density_rate" => minimum(rates),
"finest_density_error" => errors_rho[end],
"by_min" => minimum(By_vals),
"by_max" => maximum(By_vals),
),
artifacts = [
"brio_wu_verification_solutions.png",
"brio_wu_verification_convergence.png",
],
notes = [
"Canonical SciML execution path via sciml_problem(prob) and solve(prob, SSPRK33()).",
"This evidence entry is the mhd_ct benchmark-stage Brio-Wu reference comparison case.",
],
summary = Dict(
"grid_sizes" => grid_sizes,
"density_errors" => errors_rho,
"density_rates" => rates,
"bx_constant" => all(b -> abs(b - Bx) < 1.0e-10, Bx_vals),
"density_positive" => all(rho_hr .> 0),
),
)
endThis page was generated using Literate.jl.