Example 3: Sedov Blast

The Sedov Blast was invented in 1940s as a mathematical model of nuclear explosion. The setup is very simple: initialize a large amount of energy concentrated in a small volume and see how it expands in all directions faster than speed of sound. One has a semi-analytical solution for this problem which you can find from Rankine-Hugeniot jump conditions. (The solution is not analaytic per se because you still need to solve an ODE numerically.) There is a nice book about the subject.
module sedov
include("../src/LagrangianVoronoi.jl")
using .LagrangianVoronoi, WriteVTK, LinearAlgebra
using LaTeXStrings, DataFrames, CSV, Plots, Measures
Define constant parameters. Changing them will alter the result and you will no longer get agreement with the reference. Use examples/reference/sedov.jl
to generate a reference for different setup. An interesting aspect of the dynamics is that large density in the wake is followed by near vacuum around the ground zero. The adiabatic index gamma
is connected to the peak density by
$\frac{\rho_\mathrm{max}}{\rho_0} = \frac{\gamma + 1}{\gamma - 1}$`
so for $\gamma = 1.4$ and $\rho_0 = 1$, the density near the blast wave will jump from 1 to 6.
In the mathematical dream world, the process begins with a singularity of energy at $t=0$. But this is impossible to do numerically (and physically as well). Thus, we initially prescribe a small radius r_bomb
of energy E_bomb
. To be consistent with the analytical solution, we start our clock with t = t_bomb
, where t_bomb
is determined from a mathematical formula below.
const rho0 = 1.0
const xlims = (-1.0, 1.0)
const ylims = (-1.0, 1.0)
const N = 50 # resolution
const dr = 1.0/N
const nframes = 100
const gamma = 1.4
const P0 = 1e-8 # the background pressure (should be close to 0)
const c0 = sqrt(gamma*P0/rho0) # sound speed
const r_bomb = 0.05 # the initial blast radius
const E_bomb = 0.3 # yeild energy of the bomb
const t_bomb = sqrt(rho0/E_bomb*r_bomb^5) # the initial time of the simulation
const t_end = 1.0
const CFL = 0.1
const export_path = "results/sedov"
function ic!(p::VoronoiPolygon)
p.rho = rho0
p.mass = p.rho*area(p)
p.P = P0
p.e = 0.5*norm_squared(p.v) + p.P/(p.rho*(gamma - 1.0))
end
This function detonates the bomb by assigning huge over-pressure to all cells withing the radius r_bomb
.
function detonate_bomb!(grid::VoronoiGrid)
A_bomb = 0.0
for p in grid.polygons
r = norm(p.x)
if r < r_bomb
A_bomb += area(p)
end
end
P_bomb = (gamma-1.0)*E_bomb/A_bomb
for p in grid.polygons
r = norm(p.x)
if r < r_bomb
p.P = P_bomb
p.e = 0.5*norm_squared(p.v) + p.P/(p.rho*(gamma - 1.0))
end
end
end
This time, we shall use variable time step. It should be smallest in the inital phase of the simulation when the blast travels at huge speed. For that reason, we include time global variable t
and we shall write our own time-marching loop.
mutable struct Simulation <: SimulationWorkspace
grid::GridNS
solver::PressureSolver
E::Float64
S::Float64
t::Float64
Simulation() = begin
domain = Rectangle(xlims = xlims, ylims = ylims)
grid = GridNS(domain, dr)
populate_hex!(grid, ic! = ic!)
detonate_bomb!(grid)
solver = PressureSolver(grid)
return new(grid, solver, 0.0, 0.0, t_bomb)
end
end
function step!(sim::Simulation)
v_shock = 0.4*sim.t^(-0.6)*(E_bomb/rho0)^0.2
dt = CFL*dr/(sqrt(6.0)*v_shock)
move!(sim.grid, dt)
ideal_eos!(sim.grid, gamma; Pmin = P0)
find_pressure!(sim.solver, dt)
pressure_step!(sim.grid, dt)
find_D!(sim.grid)
viscous_step!(sim.grid, dt)
find_dv!(sim.grid, dt)
relaxation_step!(sim.grid, dt)
sim.t += dt
return
end
Let us plot the total energy and entropy.
function postproc!(sim::Simulation)
sim.E = 0.0
sim.S = 0.0
for p in sim.grid.polygons
sim.E += p.mass*p.e
sim.S += p.mass*log(abs(p.P/abs(p.rho)^gamma))
end
println("t = $(sim.t)")
println("energy = $(sim.E)")
println("entropy = $(sim.S)")
println()
end
We write our own time-marching loop and handle the file export manually. Once the simulation ends, we plot the density profile along the radial line and compare with the refence solution.
function main()
if !ispath(export_path)
mkpath(export_path)
@info "created a new path: $(export_path)"
end
pvd_c = paraview_collection(joinpath(export_path, "cells.pvd"))
pvd_p = paraview_collection(joinpath(export_path, "points.pvd"))
nframe = 0
sim = Simulation()
milestones = collect(range(t_end, t_bomb, nframes)) # save the data here
vtp_vars = (:rho, :v, :e, :P)
while sim.t < t_end
step!(sim)
if sim.t > milestones[end]
@show sim.t
postproc!(sim)
println()
filename= joinpath(export_path, "cframe$(nframe).vtp")
pvd_c[sim.t] = export_grid(sim.grid, filename, vtp_vars...)
filename= joinpath(export_path, "pframe$(nframe).vtp")
pvd_p[sim.t] = export_points(sim.grid, filename, vtp_vars...)
pop!(milestones)
nframe += 1
end
end
vtk_save(pvd_c)
vtk_save(pvd_p)
x = Float64[]
rho = Float64[]
for p in sim.grid.polygons
push!(x, norm(p.x))
push!(rho, p.rho)
end
csv_data = DataFrame(x=x, rho=rho)
CSV.write(string(export_path, "/linedata.csv"), csv_data)
plotdata()
end
function plotdata()
csv_data = CSV.read(string(export_path, "/linedata.csv"), DataFrame)
csv_ref = CSV.read("reference/sedov.csv", DataFrame)
plt = scatter(
csv_data.x,
csv_data.rho,
xlabel = L"x",
ylabel = L"rho",
label = "density",
color = :red,
markeralpha = 0.5,
bottom_margin = 5mm,
markersize = 1,
markerstrokewidth=0,
)
plot!(
plt,
csv_ref.r,
csv_ref.rho,
label = "analytic",
color = :blue,
linewidth = 2
)
savefig(plt, string(export_path, "/density.pdf"))
return
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
end
This page was generated using Literate.jl.