Skip to content
124 changes: 124 additions & 0 deletions examples/p4est_3d_dgsem/elixir_mhd_shockcapturing_subcell.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using Trixi

###############################################################################
# semidiscretization of the compressible ideal GLM-MHD equations

equations = IdealGlmMhdEquations3D(1.4)

"""
initial_condition_blast_wave(x, t, equations::IdealGlmMhdEquations3D)

Weak magnetic blast wave setup taken from Section 6.1 of the paper:
- A. M. Rueda-Ramírez, S. Hennemann, F. J. Hindenlang, A. R. Winters, G. J. Gassner (2021)
An entropy stable nodal discontinuous Galerkin method for the resistive MHD
equations. Part II: Subcell finite volume shock capturing
[doi: 10.1016/j.jcp.2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)
"""
function initial_condition_blast_wave(x, t, equations::IdealGlmMhdEquations3D)
# Center of the blast wave is selected for the domain [0, 3]^3
inicenter = (1.5, 1.5, 1.5)
x_norm = x[1] - inicenter[1]
y_norm = x[2] - inicenter[2]
z_norm = x[3] - inicenter[3]
r = sqrt(x_norm^2 + y_norm^2 + z_norm^2)

delta_0 = 0.1
r_0 = 0.3
lambda = exp(5.0 / delta_0 * (r - r_0))

p_inner = 0.9
p_outer = 5e-2
prim_inner = SVector(1.2, 0.1, 0.0, 0.1, p_inner, 1.0, 1.0, 1.0, 0.0)
prim_outer = SVector(1.2, 0.2, -0.4, 0.2, p_outer, 1.0, 1.0, 1.0, 0.0)
prim_vars = (prim_inner + lambda * prim_outer) / (1.0 + lambda)

return prim2cons(prim_vars, equations)
end
initial_condition = initial_condition_blast_wave

surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell_local_symmetric)
volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell_local_symmetric)
polydeg = 3
basis = LobattoLegendreBasis(polydeg)
limiter_idp = SubcellLimiterIDP(equations, basis;
positivity_variables_cons = ["rho"],
positivity_variables_nonlinear = [pressure])
volume_integral = VolumeIntegralSubcellLimiting(limiter_idp;
volume_flux_dg = volume_flux,
volume_flux_fv = surface_flux)
solver = DGSEM(basis, surface_flux, volume_integral)

# Mapping as described in https://arxiv.org/abs/2012.12040 but with slightly less warping.
# The mapping will be interpolated at tree level, and then refined without changing
# the geometry interpolant.
function mapping(xi_, eta_, zeta_)
# Transform input variables between -1 and 1 onto [0,3]
xi = 1.5 * xi_ + 1.5
eta = 1.5 * eta_ + 1.5
zeta = 1.5 * zeta_ + 1.5

y = eta +
3 / 11 * (cos(1.5 * pi * (2 * xi - 3) / 3) *
cos(0.5 * pi * (2 * eta - 3) / 3) *
cos(0.5 * pi * (2 * zeta - 3) / 3))

x = xi +
3 / 11 * (cos(0.5 * pi * (2 * xi - 3) / 3) *
cos(2 * pi * (2 * y - 3) / 3) *
cos(0.5 * pi * (2 * zeta - 3) / 3))

z = zeta +
3 / 11 * (cos(0.5 * pi * (2 * x - 3) / 3) *
cos(pi * (2 * y - 3) / 3) *
cos(0.5 * pi * (2 * zeta - 3) / 3))

return SVector(x, y, z)
end

trees_per_dimension = (2, 2, 2)
mesh = P4estMesh(trees_per_dimension,
polydeg = 3,
mapping = mapping,
initial_refinement_level = 2,
periodicity = true)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver)

###############################################################################
# ODE solvers, callbacks etc.

tspan = (0.0, 0.5)
ode = semidiscretize(semi, tspan)

summary_callback = SummaryCallback()

analysis_interval = 100
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)

alive_callback = AliveCallback(analysis_interval = analysis_interval)

save_solution = SaveSolutionCallback(interval = 100,
save_initial_solution = true,
save_final_solution = true,
solution_variables = cons2prim,
extra_node_variables = (:limiting_coefficient,))

cfl = 0.9
stepsize_callback = StepsizeCallback(cfl = cfl)

glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl)

callbacks = CallbackSet(summary_callback,
analysis_callback,
alive_callback,
save_solution,
stepsize_callback,
glm_speed_callback)

###############################################################################
# run the simulation
stage_callbacks = (SubcellLimiterIDPCorrection(),)

sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks);
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
callback = callbacks);
3 changes: 1 addition & 2 deletions src/equations/ideal_glm_mhd_2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1982,8 +1982,7 @@ end

# State validation for Newton-bisection method of subcell IDP limiting
@inline function Base.isvalid(u, equations::IdealGlmMhdEquations2D)
p = pressure(u, equations)
if u[1] <= 0 || p <= 0
if u[1] <= 0 || pressure(u, equations) <= 0
return false
end
return true
Expand Down
207 changes: 206 additions & 1 deletion src/equations/ideal_glm_mhd_3d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Non-symmetric two-point flux discretizing the nonconservative (source) term of
Powell and the Galilean nonconservative term associated with the GLM multiplier
of the [`IdealGlmMhdEquations3D`](@ref).

On curvilinear meshes, the implementation differs from the reference since we use the averaged
On curvilinear meshes, the implementation differs from the reference since we use the averaged
normal direction for the metrics dealiasing.

## References
Expand Down Expand Up @@ -361,6 +361,189 @@ end
return f
end

# For `VolumeIntegralSubcellLimiting` the nonconservative flux is created as a callable struct to
# enable dispatch on the type of the nonconservative term (symmetric / jump).
"""
flux_nonconservative_powell_local_symmetric(u_ll, u_rr,
normal_direction::AbstractVector,
equations::IdealGlmMhdEquations3D)

Non-symmetric two-point flux discretizing the nonconservative (source) term of
Powell and the Galilean nonconservative term associated with the GLM multiplier
of the [`IdealGlmMhdEquations3D`](@ref).

This implementation uses a non-conservative term that can be written as the product
of local and symmetric parts. It is equivalent to the non-conservative flux of Bohm
et al. [`flux_nonconservative_powell`](@ref) for conforming meshes but it yields different
results on non-conforming meshes(!). On curvilinear meshes this formulation applies the
local normal direction compared to the averaged one used in [`flux_nonconservative_powell`](@ref).

The two other flux functions with the same name return either the local
or symmetric portion of the non-conservative flux based on the type of the
nonconservative_type argument, employing multiple dispatch. They are used to
compute the subcell fluxes in dg_3d_subcell_limiters.jl.

## References
- Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts
Discretizations of Non-Conservative Systems.
[DOI: 10.1016/j.jcp.2023.112607](https://doi.org/10.1016/j.jcp.2023.112607).
[arXiv: 2211.14009](https://doi.org/10.48550/arXiv.2211.14009).
"""
@inline function (noncons_flux::FluxNonConservativePowellLocalSymmetric)(u_ll, u_rr,
normal_direction::AbstractVector,
equations::IdealGlmMhdEquations3D)
rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll
rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr, psi_rr = u_rr

v1_ll = rho_v1_ll / rho_ll
v2_ll = rho_v2_ll / rho_ll
v3_ll = rho_v3_ll / rho_ll
v_dot_B_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll

# The factor 0.5 of the averages can be omitted since it is already applied when this
# function is called.
psi_avg = (psi_ll + psi_rr)
B1_avg = (B1_ll + B1_rr)
B2_avg = (B2_ll + B2_rr)
B3_avg = (B3_ll + B3_rr)

B_dot_n_avg = B1_avg * normal_direction[1] + B2_avg * normal_direction[2] +
B3_avg * normal_direction[3]
v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] +
v3_ll * normal_direction[3]

# Powell nonconservative term: (0, B_1, B_2, B_3, v⋅B, v_1, v_2, v_3, 0)
# Galilean nonconservative term: (0, 0, 0, 0, ψ v_{1,2,3}, 0, 0, 0, v_{1,2,3})
f = SVector(0,
B1_ll * B_dot_n_avg,
B2_ll * B_dot_n_avg,
B3_ll * B_dot_n_avg,
v_dot_B_ll * B_dot_n_avg + v_dot_n_ll * psi_ll * psi_avg,
v1_ll * B_dot_n_avg,
v2_ll * B_dot_n_avg,
v3_ll * B_dot_n_avg,
v_dot_n_ll * psi_avg)

return f
end

"""
flux_nonconservative_powell_local_symmetric(u_ll, normal_direction_ll::AbstractVector,
equations::IdealGlmMhdEquations3D,
nonconservative_type::NonConservativeLocal,
nonconservative_term::Integer)

Local part of the Powell and GLM non-conservative terms. Needed for the calculation of
the non-conservative staggered "fluxes" for subcell limiting. See, e.g.,
- Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts
Discretizations of Non-Conservative Systems.
[DOI: 10.1016/j.jcp.2023.112607](https://doi.org/10.1016/j.jcp.2023.112607).
[arXiv: 2211.14009](https://doi.org/10.48550/arXiv.2211.14009).

This function is used to compute the subcell fluxes in dg_3d_subcell_limiters.jl.
"""
@inline function (noncons_flux::FluxNonConservativePowellLocalSymmetric)(u_ll,
normal_direction_ll::AbstractVector,
equations::IdealGlmMhdEquations3D,
nonconservative_type::NonConservativeLocal,
nonconservative_term::Integer)
rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll

if nonconservative_term == 1
# Powell nonconservative term: (0, B_1, B_2, B_3, v⋅B, v_1, v_2, v_3, 0)
v1_ll = rho_v1_ll / rho_ll
v2_ll = rho_v2_ll / rho_ll
v3_ll = rho_v3_ll / rho_ll
v_dot_B_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll

f = SVector(0,
B1_ll,
B2_ll,
B3_ll,
v_dot_B_ll,
v1_ll,
v2_ll,
v3_ll,
0)
else # nonconservative_term == 2
# Galilean nonconservative term: (0, 0, 0, 0, ψ v_{1,2}, 0, 0, 0, v_{1,2})
v1_ll = rho_v1_ll / rho_ll
v2_ll = rho_v2_ll / rho_ll
v3_ll = rho_v3_ll / rho_ll
v_dot_n_ll = v1_ll * normal_direction_ll[1] + v2_ll * normal_direction_ll[2] +
v3_ll * normal_direction_ll[3]

f = SVector(0,
0,
0,
0,
v_dot_n_ll * psi_ll,
0,
0,
0,
v_dot_n_ll)
end
return f
end

"""
flux_nonconservative_powell_local_symmetric(u_ll, normal_direction_avg::AbstractVector,
equations::IdealGlmMhdEquations3D,
nonconservative_type::NonConservativeSymmetric,
nonconservative_term::Integer)

Symmetric part of the Powell and GLM non-conservative terms. Needed for the calculation of
the non-conservative staggered "fluxes" for subcell limiting. See, e.g.,
- Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts
Discretizations of Non-Conservative Systems.
[DOI: 10.1016/j.jcp.2023.112607](https://doi.org/10.1016/j.jcp.2023.112607).
[arXiv: 2211.14009](https://doi.org/10.48550/arXiv.2211.14009).

This function is used to compute the subcell fluxes in dg_3d_subcell_limiters.jl.
"""
@inline function (noncons_flux::FluxNonConservativePowellLocalSymmetric)(u_ll, u_rr,
normal_direction_avg::AbstractVector,
equations::IdealGlmMhdEquations3D,
nonconservative_type::NonConservativeSymmetric,
nonconservative_term::Integer)
rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll
rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr, psi_rr = u_rr

if nonconservative_term == 1
# Powell nonconservative term: (0, B_1, B_2, B_3, v⋅B, v_1, v_2, v_3, 0)
# The factor 0.5 of the average can be omitted since it is already applied when this
# function is called.
B_dot_n_avg = ((B1_ll + B1_rr) * normal_direction_avg[1] +
(B2_ll + B2_rr) * normal_direction_avg[2] +
(B3_ll + B3_rr) * normal_direction_avg[3])
f = SVector(0,
B_dot_n_avg,
B_dot_n_avg,
B_dot_n_avg,
B_dot_n_avg,
B_dot_n_avg,
B_dot_n_avg,
B_dot_n_avg,
0)
else # nonconservative_term == 2
# Galilean nonconservative term: (0, 0, 0, 0, ψ v_{1,2}, 0, 0, 0, v_{1,2})
# The factor 0.5 of the average can be omitted since it is already applied when this
# function is called.
psi_avg = (psi_ll + psi_rr)
f = SVector(0,
0,
0,
0,
psi_avg,
0,
0,
0,
psi_avg)
end

return f
end

"""
flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations3D)

Expand Down Expand Up @@ -1130,6 +1313,20 @@ p &= (\gamma - 1) \left( E_\mathrm{tot} - E_\mathrm{kin} - E_\mathrm{mag} - \fra
return p
end

# Transformation from conservative variables u to d(p)/d(u)
@inline function gradient_conservative(::typeof(pressure),
u, equations::IdealGlmMhdEquations3D)
rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u

v1 = rho_v1 / rho
v2 = rho_v2 / rho
v3 = rho_v3 / rho
v_square = v1^2 + v2^2 + v3^2

return (equations.gamma - 1) *
SVector(0.5f0 * v_square, -v1, -v2, -v3, 1, -B1, -B2, -B3, -psi)
end

@inline function density_pressure(u, equations::IdealGlmMhdEquations3D)
rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u
rho_times_p = (equations.gamma - 1) * (rho * rho_e -
Expand Down Expand Up @@ -1407,6 +1604,14 @@ end
cons[9]^2 / 2)
end

# State validation for Newton-bisection method of subcell IDP limiting
@inline function Base.isvalid(u, equations::IdealGlmMhdEquations3D)
if u[1] <= 0 || pressure(u, equations) <= 0
return false
end
return true
end

# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons'
@inline function cross_helicity(cons, ::IdealGlmMhdEquations3D)
return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1]
Expand Down
Loading
Loading